ERP-node/docs/phase0-migration-strategy.md

8.3 KiB

Phase 0: 데이터 마이그레이션 전략

1. 현재 데이터 구조 분석

screen_layouts.properties 구조

{
  // 기본 정보
  "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 비파괴적 전환 (권장)

기존 필드를 유지하면서 새로운 필드를 추가하는 방식

{
  // 기존 필드 유지 (하위 호환성)
  "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 렌더링 로직 수정

// 렌더러에서 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

// 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

// 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

// 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

// 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 자동 마이그레이션 함수

// 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 마이그레이션 스크립트

-- 마이그레이션 백업 테이블 생성
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 롤백 스크립트

-- 마이그레이션 전 상태로 복원
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 단계적 롤백

// 특정 화면만 롤백
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. 테스트 필수: 각 마이그레이션 단계별 화면 테스트