394 lines
8.3 KiB
Markdown
394 lines
8.3 KiB
Markdown
# Phase 0: 데이터 마이그레이션 전략
|
|
|
|
## 1. 현재 데이터 구조 분석
|
|
|
|
### screen_layouts.properties 구조
|
|
|
|
```jsonc
|
|
{
|
|
// 기본 정보
|
|
"type": "component",
|
|
"componentType": "text-input", // 기존 컴포넌트 타입
|
|
|
|
// 위치/크기
|
|
"position": { "x": 68, "y": 80, "z": 1 },
|
|
"size": { "width": 324, "height": 40 },
|
|
|
|
// 라벨 및 스타일
|
|
"label": "품목코드",
|
|
"style": {
|
|
"labelColor": "#000000",
|
|
"labelDisplay": true,
|
|
"labelFontSize": "14px",
|
|
"labelFontWeight": "500",
|
|
"labelMarginBottom": "8px"
|
|
},
|
|
|
|
// 데이터 바인딩
|
|
"tableName": "order_table",
|
|
"columnName": "part_code",
|
|
|
|
// 필드 속성
|
|
"required": true,
|
|
"readonly": false,
|
|
|
|
// 컴포넌트별 설정
|
|
"componentConfig": {
|
|
"type": "text-input",
|
|
"format": "none",
|
|
"webType": "text",
|
|
"multiline": false,
|
|
"placeholder": "텍스트를 입력하세요"
|
|
},
|
|
|
|
// 그리드 레이아웃
|
|
"gridColumns": 5,
|
|
"gridRowIndex": 0,
|
|
"gridColumnStart": 1,
|
|
"gridColumnSpan": "third",
|
|
|
|
// 기타
|
|
"parentId": null
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## 2. 마이그레이션 전략: 하이브리드 방식
|
|
|
|
### 2.1 비파괴적 전환 (권장)
|
|
|
|
기존 필드를 유지하면서 새로운 필드를 추가하는 방식
|
|
|
|
```jsonc
|
|
{
|
|
// 기존 필드 유지 (하위 호환성)
|
|
"componentType": "text-input",
|
|
"componentConfig": { ... },
|
|
|
|
// 신규 필드 추가
|
|
"unifiedType": "UnifiedInput", // 새로운 통합 컴포넌트 타입
|
|
"unifiedConfig": { // 새로운 설정 구조
|
|
"type": "text",
|
|
"format": "none",
|
|
"placeholder": "텍스트를 입력하세요"
|
|
},
|
|
|
|
// 마이그레이션 메타데이터
|
|
"_migration": {
|
|
"version": "2.0",
|
|
"migratedAt": "2024-12-19T00:00:00Z",
|
|
"migratedBy": "system",
|
|
"originalType": "text-input"
|
|
}
|
|
}
|
|
```
|
|
|
|
### 2.2 렌더링 로직 수정
|
|
|
|
```typescript
|
|
// 렌더러에서 unifiedType 우선 사용
|
|
function renderComponent(props: ComponentProps) {
|
|
// 신규 타입이 있으면 Unified 컴포넌트 사용
|
|
if (props.unifiedType) {
|
|
return <UnifiedComponentRenderer
|
|
type={props.unifiedType}
|
|
config={props.unifiedConfig}
|
|
/>;
|
|
}
|
|
|
|
// 없으면 기존 레거시 컴포넌트 사용
|
|
return <LegacyComponentRenderer
|
|
type={props.componentType}
|
|
config={props.componentConfig}
|
|
/>;
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## 3. 컴포넌트별 매핑 규칙
|
|
|
|
### 3.1 text-input → UnifiedInput
|
|
|
|
```typescript
|
|
// AS-IS
|
|
{
|
|
"componentType": "text-input",
|
|
"componentConfig": {
|
|
"type": "text-input",
|
|
"format": "none",
|
|
"webType": "text",
|
|
"multiline": false,
|
|
"placeholder": "텍스트를 입력하세요"
|
|
}
|
|
}
|
|
|
|
// TO-BE
|
|
{
|
|
"unifiedType": "UnifiedInput",
|
|
"unifiedConfig": {
|
|
"type": "text", // componentConfig.webType 또는 "text"
|
|
"format": "none", // componentConfig.format
|
|
"placeholder": "..." // componentConfig.placeholder
|
|
}
|
|
}
|
|
```
|
|
|
|
### 3.2 number-input → UnifiedInput
|
|
|
|
```typescript
|
|
// AS-IS
|
|
{
|
|
"componentType": "number-input",
|
|
"componentConfig": {
|
|
"type": "number-input",
|
|
"webType": "number",
|
|
"min": 0,
|
|
"max": 100,
|
|
"step": 1
|
|
}
|
|
}
|
|
|
|
// TO-BE
|
|
{
|
|
"unifiedType": "UnifiedInput",
|
|
"unifiedConfig": {
|
|
"type": "number",
|
|
"min": 0,
|
|
"max": 100,
|
|
"step": 1
|
|
}
|
|
}
|
|
```
|
|
|
|
### 3.3 select-basic → UnifiedSelect
|
|
|
|
```typescript
|
|
// AS-IS (code 타입)
|
|
{
|
|
"componentType": "select-basic",
|
|
"codeCategory": "ORDER_STATUS",
|
|
"componentConfig": {
|
|
"type": "select-basic",
|
|
"webType": "code",
|
|
"codeCategory": "ORDER_STATUS"
|
|
}
|
|
}
|
|
|
|
// TO-BE
|
|
{
|
|
"unifiedType": "UnifiedSelect",
|
|
"unifiedConfig": {
|
|
"mode": "dropdown",
|
|
"source": "code",
|
|
"codeGroup": "ORDER_STATUS"
|
|
}
|
|
}
|
|
|
|
// AS-IS (entity 타입)
|
|
{
|
|
"componentType": "select-basic",
|
|
"componentConfig": {
|
|
"type": "select-basic",
|
|
"webType": "entity",
|
|
"searchable": true,
|
|
"valueField": "id",
|
|
"displayField": "name"
|
|
}
|
|
}
|
|
|
|
// TO-BE
|
|
{
|
|
"unifiedType": "UnifiedSelect",
|
|
"unifiedConfig": {
|
|
"mode": "dropdown",
|
|
"source": "entity",
|
|
"searchable": true,
|
|
"valueField": "id",
|
|
"displayField": "name"
|
|
}
|
|
}
|
|
```
|
|
|
|
### 3.4 date-input → UnifiedDate
|
|
|
|
```typescript
|
|
// AS-IS
|
|
{
|
|
"componentType": "date-input",
|
|
"componentConfig": {
|
|
"type": "date-input",
|
|
"webType": "date",
|
|
"format": "YYYY-MM-DD"
|
|
}
|
|
}
|
|
|
|
// TO-BE
|
|
{
|
|
"unifiedType": "UnifiedDate",
|
|
"unifiedConfig": {
|
|
"type": "date",
|
|
"format": "YYYY-MM-DD"
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## 4. 마이그레이션 스크립트
|
|
|
|
### 4.1 자동 마이그레이션 함수
|
|
|
|
```typescript
|
|
// lib/migration/componentMigration.ts
|
|
|
|
interface MigrationResult {
|
|
success: boolean;
|
|
unifiedType: string;
|
|
unifiedConfig: Record<string, any>;
|
|
}
|
|
|
|
export function migrateToUnified(
|
|
componentType: string,
|
|
componentConfig: Record<string, any>
|
|
): MigrationResult {
|
|
|
|
switch (componentType) {
|
|
case 'text-input':
|
|
return {
|
|
success: true,
|
|
unifiedType: 'UnifiedInput',
|
|
unifiedConfig: {
|
|
type: componentConfig.webType || 'text',
|
|
format: componentConfig.format || 'none',
|
|
placeholder: componentConfig.placeholder
|
|
}
|
|
};
|
|
|
|
case 'number-input':
|
|
return {
|
|
success: true,
|
|
unifiedType: 'UnifiedInput',
|
|
unifiedConfig: {
|
|
type: 'number',
|
|
min: componentConfig.min,
|
|
max: componentConfig.max,
|
|
step: componentConfig.step
|
|
}
|
|
};
|
|
|
|
case 'select-basic':
|
|
return {
|
|
success: true,
|
|
unifiedType: 'UnifiedSelect',
|
|
unifiedConfig: {
|
|
mode: 'dropdown',
|
|
source: componentConfig.webType || 'static',
|
|
codeGroup: componentConfig.codeCategory,
|
|
searchable: componentConfig.searchable,
|
|
valueField: componentConfig.valueField,
|
|
displayField: componentConfig.displayField
|
|
}
|
|
};
|
|
|
|
case 'date-input':
|
|
return {
|
|
success: true,
|
|
unifiedType: 'UnifiedDate',
|
|
unifiedConfig: {
|
|
type: componentConfig.webType || 'date',
|
|
format: componentConfig.format
|
|
}
|
|
};
|
|
|
|
default:
|
|
return {
|
|
success: false,
|
|
unifiedType: '',
|
|
unifiedConfig: {}
|
|
};
|
|
}
|
|
}
|
|
```
|
|
|
|
### 4.2 DB 마이그레이션 스크립트
|
|
|
|
```sql
|
|
-- 마이그레이션 백업 테이블 생성
|
|
CREATE TABLE screen_layouts_backup_v2 AS
|
|
SELECT * FROM screen_layouts;
|
|
|
|
-- 마이그레이션 실행 (text-input 예시)
|
|
UPDATE screen_layouts
|
|
SET properties = properties || jsonb_build_object(
|
|
'unifiedType', 'UnifiedInput',
|
|
'unifiedConfig', jsonb_build_object(
|
|
'type', COALESCE(properties->'componentConfig'->>'webType', 'text'),
|
|
'format', COALESCE(properties->'componentConfig'->>'format', 'none'),
|
|
'placeholder', properties->'componentConfig'->>'placeholder'
|
|
),
|
|
'_migration', jsonb_build_object(
|
|
'version', '2.0',
|
|
'migratedAt', NOW(),
|
|
'originalType', 'text-input'
|
|
)
|
|
)
|
|
WHERE properties->>'componentType' = 'text-input';
|
|
```
|
|
|
|
---
|
|
|
|
## 5. 롤백 전략
|
|
|
|
### 5.1 롤백 스크립트
|
|
|
|
```sql
|
|
-- 마이그레이션 전 상태로 복원
|
|
UPDATE screen_layouts sl
|
|
SET properties = slb.properties
|
|
FROM screen_layouts_backup_v2 slb
|
|
WHERE sl.layout_id = slb.layout_id;
|
|
|
|
-- 또는 신규 필드만 제거
|
|
UPDATE screen_layouts
|
|
SET properties = properties - 'unifiedType' - 'unifiedConfig' - '_migration';
|
|
```
|
|
|
|
### 5.2 단계적 롤백
|
|
|
|
```typescript
|
|
// 특정 화면만 롤백
|
|
async function rollbackScreen(screenId: number) {
|
|
await db.query(`
|
|
UPDATE screen_layouts sl
|
|
SET properties = properties - 'unifiedType' - 'unifiedConfig' - '_migration'
|
|
WHERE screen_id = $1
|
|
`, [screenId]);
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## 6. 마이그레이션 일정
|
|
|
|
| 단계 | 작업 | 대상 | 시점 |
|
|
|:---:|:---|:---|:---|
|
|
| 1 | 백업 테이블 생성 | 전체 | Phase 1 시작 전 |
|
|
| 2 | UnifiedInput 마이그레이션 | text-input, number-input | Phase 1 중 |
|
|
| 3 | UnifiedSelect 마이그레이션 | select-basic | Phase 1 중 |
|
|
| 4 | UnifiedDate 마이그레이션 | date-input | Phase 1 중 |
|
|
| 5 | 검증 및 테스트 | 전체 | Phase 1 완료 후 |
|
|
| 6 | 레거시 필드 제거 | 전체 | Phase 5 (추후) |
|
|
|
|
---
|
|
|
|
## 7. 주의사항
|
|
|
|
1. **항상 백업 먼저**: 마이그레이션 전 반드시 백업 테이블 생성
|
|
2. **점진적 전환**: 한 번에 모든 컴포넌트를 마이그레이션하지 않음
|
|
3. **하위 호환성**: 기존 필드 유지로 롤백 가능하게
|
|
4. **테스트 필수**: 각 마이그레이션 단계별 화면 테스트
|
|
|
|
|