Files
vexplor/DETAILED_FILE_MIGRATION_PLAN.md
kjs ed78ef184d feat: Complete Phase 1 of Prisma to Raw Query migration
Phase 1 완료: Raw Query 기반 데이터베이스 아키텍처 구축

 구현 완료 내용:
- DatabaseManager 클래스 구현 (연결 풀, 트랜잭션 관리)
- QueryBuilder 유틸리티 (동적 쿼리 생성)
- 타입 정의 및 검증 로직 (database.ts, databaseValidator.ts)
- 단위 테스트 작성 및 통과

🔧 전환 완료 서비스:
- externalCallConfigService.ts (Raw Query 전환)
- multiConnectionQueryService.ts (Raw Query 전환)

📚 문서:
- PHASE1_USAGE_GUIDE.md (사용 가이드)
- DETAILED_FILE_MIGRATION_PLAN.md (상세 계획)
- PRISMA_TO_RAW_QUERY_MIGRATION_PLAN.md (Phase 1 완료 표시)

🧪 테스트:
- database.test.ts (핵심 기능 테스트)
- 모든 테스트 통과 확인

이제 Phase 2 (핵심 서비스 전환)로 진행 가능

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-09-30 15:29:20 +09:00

32 KiB

📋 파일별 상세 Prisma → Raw Query 마이그레이션 계획

🎯 개요

총 42개 파일, 444개 Prisma 호출을 Raw Query로 전환하는 상세 계획입니다. 각 파일의 복잡도, 의존성, 전환 전략을 분석하여 기능 손실 없는 완전한 마이그레이션을 수행합니다.


🔴 Phase 2: 핵심 서비스 전환 (107개 호출)

1. screenManagementService.ts (46개 호출) 최우선

📊 현재 상태 분석

  • 복잡도: 매우 높음 (JSON 처리, 복잡한 조인, 트랜잭션)
  • 주요 기능: 화면 정의 관리, 레이아웃 저장/불러오기, 메뉴 할당
  • 의존성: screen_definitions, screen_components, screen_layouts 등

🔧 전환 전략

1.1 기본 CRUD 작업 전환
// 기존 Prisma 코드
const screen = await prisma.screen_definitions.create({
  data: {
    screen_name: screenData.screenName,
    screen_code: screenData.screenCode,
    table_name: screenData.tableName,
    company_code: screenData.companyCode,
    description: screenData.description,
    created_by: screenData.createdBy,
  },
});

// 새로운 Raw Query 코드
const { query, params } = QueryBuilder.insert(
  "screen_definitions",
  {
    screen_name: screenData.screenName,
    screen_code: screenData.screenCode,
    table_name: screenData.tableName,
    company_code: screenData.companyCode,
    description: screenData.description,
    created_by: screenData.createdBy,
    created_at: new Date(),
    updated_at: new Date(),
  },
  {
    returning: ["*"],
  }
);
const [screen] = await DatabaseManager.query(query, params);
1.2 복잡한 조인 쿼리 전환
// 기존 Prisma 코드 (복잡한 include)
const screens = await prisma.screen_definitions.findMany({
  where: whereClause,
  include: {
    screen_components: true,
    screen_layouts: true,
  },
  orderBy: { created_at: "desc" },
  skip: (page - 1) * size,
  take: size,
});

// 새로운 Raw Query 코드
const { query, params } = QueryBuilder.select("screen_definitions", {
  columns: [
    "sd.*",
    "json_agg(DISTINCT sc.*) as screen_components",
    "json_agg(DISTINCT sl.*) as screen_layouts",
  ],
  joins: [
    {
      type: "LEFT",
      table: "screen_components sc",
      on: "sd.id = sc.screen_id",
    },
    {
      type: "LEFT",
      table: "screen_layouts sl",
      on: "sd.id = sl.screen_id",
    },
  ],
  where: whereClause,
  orderBy: "sd.created_at DESC",
  limit: size,
  offset: (page - 1) * size,
  groupBy: ["sd.id"],
});
const screens = await DatabaseManager.query(query, params);
1.3 JSON 데이터 처리 전환
// 기존 Prisma 코드 (JSON 검색)
const screens = await prisma.screen_definitions.findMany({
  where: {
    screen_config: { path: ["type"], equals: "form" },
  },
});

// 새로운 Raw Query 코드
const screens = await DatabaseManager.query(
  `
  SELECT * FROM screen_definitions 
  WHERE screen_config->>'type' = $1
`,
  ["form"]
);
1.4 트랜잭션 처리 전환
// 기존 Prisma 트랜잭션
await prisma.$transaction(async (tx) => {
  const screen = await tx.screen_definitions.create({ data: screenData });
  await tx.screen_components.createMany({ data: components });
  return screen;
});

// 새로운 Raw Query 트랜잭션
await DatabaseManager.transaction(async (client) => {
  const screenResult = await client.query(
    "INSERT INTO screen_definitions (...) VALUES (...) RETURNING *",
    screenParams
  );
  const screen = screenResult.rows[0];

  for (const component of components) {
    await client.query(
      "INSERT INTO screen_components (...) VALUES (...)",
      componentParams
    );
  }

  return screen;
});

🧪 테스트 전략

  1. 단위 테스트: 각 메서드별 입출력 검증
  2. 통합 테스트: 화면 생성 → 조회 → 수정 → 삭제 전체 플로우
  3. JSON 데이터 테스트: 복잡한 레이아웃 데이터 저장/복원
  4. 성능 테스트: 대량 화면 데이터 처리 성능 비교

⚠️ 주의사항

  • JSON 데이터 타입 변환 주의 (PostgreSQL JSONB ↔ JavaScript Object)
  • 날짜 타입 변환 (Prisma DateTime ↔ PostgreSQL timestamp)
  • NULL 값 처리 일관성 유지

2. tableManagementService.ts (35개 호출)

📊 현재 상태 분석

  • 복잡도: 매우 높음 (메타데이터 조회, DDL 실행, 동적 쿼리)
  • 주요 기능: 테이블/컬럼 정보 관리, 동적 테이블 생성, 메타데이터 캐싱
  • 의존성: information_schema, table_labels, column_labels

🔧 전환 전략

2.1 메타데이터 조회 (이미 Raw Query 사용 중)
// 현재 코드 (이미 Raw Query)
const rawTables = await prisma.$queryRaw<any[]>`
  SELECT 
    t.table_name as "tableName",
    COALESCE(tl.table_label, t.table_name) as "displayName"
  FROM information_schema.tables t
  LEFT JOIN table_labels tl ON t.table_name = tl.table_name
  WHERE t.table_schema = 'public'
`;

// 새로운 Raw Query 코드 (Prisma 제거)
const rawTables = await DatabaseManager.query(`
  SELECT 
    t.table_name as "tableName",
    COALESCE(tl.table_label, t.table_name) as "displayName"
  FROM information_schema.tables t
  LEFT JOIN table_labels tl ON t.table_name = tl.table_name
  WHERE t.table_schema = 'public'
`);
2.2 동적 테이블 데이터 조회
// 기존 Prisma 코드 (동적 테이블명)
const data = await prisma.$queryRawUnsafe(
  `SELECT * FROM ${tableName} WHERE id = $1`,
  [id]
);

// 새로운 Raw Query 코드
const data = await DatabaseManager.queryUnsafe(
  `SELECT * FROM ${DatabaseValidator.sanitizeTableName(
    tableName
  )} WHERE id = $1`,
  [id]
);
2.3 UPSERT 작업 전환
// 기존 Prisma UPSERT
await prisma.table_labels.upsert({
  where: { table_name: tableName },
  update: { table_label: label, updated_date: new Date() },
  create: {
    table_name: tableName,
    table_label: label,
    created_date: new Date(),
  },
});

// 새로운 Raw Query UPSERT
const { query, params } = QueryBuilder.insert(
  "table_labels",
  {
    table_name: tableName,
    table_label: label,
    created_date: new Date(),
    updated_date: new Date(),
  },
  {
    onConflict: {
      columns: ["table_name"],
      action: "DO UPDATE",
      updateSet: ["table_label", "updated_date"],
    },
    returning: ["*"],
  }
);
await DatabaseManager.query(query, params);

🧪 테스트 전략

  1. 메타데이터 테스트: information_schema 조회 결과 일치성
  2. 동적 쿼리 테스트: 다양한 테이블명/컬럼명으로 안전성 검증
  3. DDL 테스트: 테이블 생성/수정/삭제 기능
  4. 캐시 테스트: 메타데이터 캐싱 동작 검증

3. dataflowService.ts (31개 호출)

📊 현재 상태 분석

  • 복잡도: 높음 (관계 관리, 복잡한 비즈니스 로직)
  • 주요 기능: 테이블 관계 관리, 데이터플로우 다이어그램
  • 의존성: table_relationships, dataflow_diagrams

🔧 전환 전략

3.1 관계 생성 로직
// 기존 Prisma 코드
const maxDiagramId = await prisma.table_relationships.findFirst({
  where: { company_code: data.companyCode },
  orderBy: { diagram_id: "desc" },
  select: { diagram_id: true },
});

const relationship = await prisma.table_relationships.create({
  data: {
    diagram_id: diagramId,
    relationship_name: data.relationshipName,
    // ... 기타 필드
  },
});

// 새로운 Raw Query 코드
const maxDiagramResult = await DatabaseManager.query(
  `
  SELECT diagram_id FROM table_relationships 
  WHERE company_code = $1 
  ORDER BY diagram_id DESC 
  LIMIT 1
`,
  [data.companyCode]
);

const diagramId = (maxDiagramResult[0]?.diagram_id || 0) + 1;

const { query, params } = QueryBuilder.insert(
  "table_relationships",
  {
    diagram_id: diagramId,
    relationship_name: data.relationshipName,
    // ... 기타 필드
  },
  { returning: ["*"] }
);

const [relationship] = await DatabaseManager.query(query, params);

🧪 테스트 전략

  1. 관계 생성 테스트: 다양한 테이블 관계 패턴
  2. 중복 검증 테스트: 동일 관계 생성 방지
  3. 다이어그램 테스트: 복잡한 관계도 생성/조회

🟡 Phase 3: 관리 기능 전환 (162개 호출)

4. multilangService.ts (25개 호출)

📊 현재 상태 분석

  • 복잡도: 높음 (재귀 쿼리, 다국어 처리)
  • 주요 기능: 다국어 번역 관리, 계층 구조 처리
  • 의존성: multilang_translations, 재귀 관계

🔧 전환 전략

4.1 재귀 쿼리 전환
// 기존 Prisma 코드 (재귀 관계 조회)
const translations = await prisma.multilang_translations.findMany({
  where: { parent_id: null },
  include: {
    children: {
      include: {
        children: true, // 중첩 include
      },
    },
  },
});

// 새로운 Raw Query 코드 (WITH RECURSIVE)
const translations = await DatabaseManager.query(`
  WITH RECURSIVE translation_tree AS (
    SELECT *, 0 as level 
    FROM multilang_translations 
    WHERE parent_id IS NULL
    
    UNION ALL
    
    SELECT t.*, tt.level + 1
    FROM multilang_translations t
    JOIN translation_tree tt ON t.parent_id = tt.id
  )
  SELECT * FROM translation_tree 
  ORDER BY level, sort_order
`);

🧪 테스트 전략

  1. 재귀 쿼리 테스트: 깊은 계층 구조 처리
  2. 다국어 테스트: 다양한 언어 코드 처리
  3. 성능 테스트: 대량 번역 데이터 처리

5. batchService.ts (16개 호출)

📊 현재 상태 분석

  • 복잡도: 중간 (배치 작업 관리)
  • 주요 기능: 배치 작업 스케줄링, 실행 이력 관리
  • 의존성: batch_configs, batch_execution_logs

🔧 전환 전략

5.1 배치 설정 관리
// 기존 Prisma 코드
const batchConfigs = await prisma.batch_configs.findMany({
  where: { is_active: true },
  include: { execution_logs: { take: 10, orderBy: { created_at: "desc" } } },
});

// 새로운 Raw Query 코드
const batchConfigs = await DatabaseManager.query(`
  SELECT 
    bc.*,
    json_agg(
      json_build_object(
        'id', bel.id,
        'status', bel.status,
        'created_at', bel.created_at
      ) ORDER BY bel.created_at DESC
    ) FILTER (WHERE bel.id IS NOT NULL) as execution_logs
  FROM batch_configs bc
  LEFT JOIN (
    SELECT DISTINCT ON (batch_config_id) 
      batch_config_id, id, status, created_at,
      ROW_NUMBER() OVER (PARTITION BY batch_config_id ORDER BY created_at DESC) as rn
    FROM batch_execution_logs
  ) bel ON bc.id = bel.batch_config_id AND bel.rn <= 10
  WHERE bc.is_active = true
  GROUP BY bc.id
`);

7. dynamicFormService.ts (15개 호출)

📊 현재 상태 분석

  • 복잡도: 높음 (UPSERT, 동적 테이블 처리, 타입 변환)
  • 주요 기능: 동적 폼 데이터 저장/조회, 데이터 검증, 타입 변환
  • 의존성: 동적 테이블들, form_data, 이벤트 트리거

🔧 전환 전략

7.1 동적 UPSERT 로직 전환
// 기존 Prisma 코드 (동적 테이블 UPSERT)
const existingRecord = await prisma.$queryRawUnsafe(
  `SELECT * FROM ${tableName} WHERE id = $1`,
  [id]
);

if (existingRecord.length > 0) {
  await prisma.$executeRawUnsafe(updateQuery, updateValues);
} else {
  await prisma.$executeRawUnsafe(insertQuery, insertValues);
}

// 새로운 Raw Query 코드 (PostgreSQL UPSERT)
const upsertQuery = `
  INSERT INTO ${DatabaseValidator.sanitizeTableName(tableName)} 
  (${columns.join(", ")}) 
  VALUES (${placeholders.join(", ")})
  ON CONFLICT (id) 
  DO UPDATE SET 
    ${updateColumns.map((col) => `${col} = EXCLUDED.${col}`).join(", ")},
    updated_at = NOW()
  RETURNING *
`;

const [result] = await DatabaseManager.query(upsertQuery, values);
7.2 타입 변환 로직 강화
// 기존 타입 변환 (Prisma 자동 처리)
const data = await prisma.someTable.create({ data: formData });

// 새로운 타입 변환 (명시적 처리)
const convertedData = this.convertFormDataForPostgreSQL(formData, tableSchema);
const { query, params } = QueryBuilder.insert(tableName, convertedData, {
  returning: ["*"]
});
const [result] = await DatabaseManager.query(query, params);

// 타입 변환 함수 강화
private convertFormDataForPostgreSQL(data: any, schema: TableColumn[]): any {
  const converted = {};

  for (const [key, value] of Object.entries(data)) {
    const column = schema.find(col => col.columnName === key);
    if (column) {
      converted[key] = this.convertValueByType(value, column.dataType);
    }
  }

  return converted;
}
7.3 동적 검증 로직
// 기존 Prisma 검증 (스키마 기반)
const validation = await prisma.$validator.validate(data, schema);

// 새로운 Raw Query 검증
async validateFormData(data: any, tableName: string): Promise<ValidationResult> {
  // 테이블 스키마 조회
  const schema = await this.getTableSchema(tableName);

  const errors: ValidationError[] = [];

  for (const column of schema) {
    const value = data[column.columnName];

    // NULL 검증
    if (!column.nullable && (value === null || value === undefined)) {
      errors.push({
        field: column.columnName,
        message: `${column.columnName}은(는) 필수 입력 항목입니다.`,
        code: 'REQUIRED_FIELD'
      });
    }

    // 타입 검증
    if (value !== null && !this.isValidType(value, column.dataType)) {
      errors.push({
        field: column.columnName,
        message: `${column.columnName}의 데이터 타입이 올바르지 않습니다.`,
        code: 'INVALID_TYPE'
      });
    }
  }

  return { valid: errors.length === 0, errors };
}

🧪 테스트 전략

  1. UPSERT 테스트: 신규 생성 vs 기존 업데이트 시나리오
  2. 타입 변환 테스트: 다양한 PostgreSQL 타입 변환
  3. 검증 테스트: 필수 필드, 타입 검증, 길이 제한
  4. 동적 테이블 테스트: 런타임에 생성된 테이블 처리

8. externalDbConnectionService.ts (15개 호출)

📊 현재 상태 분석

  • 복잡도: 높음 (다중 DB 연결, 외부 시스템 연동)
  • 주요 기능: 외부 데이터베이스 연결 관리, 스키마 동기화
  • 의존성: external_db_connections, connection_pools

🔧 전환 전략

8.1 연결 설정 관리
// 기존 Prisma 코드
const connections = await prisma.external_db_connections.findMany({
  where: { is_active: true },
  include: { connection_pools: true },
});

// 새로운 Raw Query 코드
const connections = await DatabaseManager.query(`
  SELECT 
    edc.*,
    json_agg(cp.*) as connection_pools
  FROM external_db_connections edc
  LEFT JOIN connection_pools cp ON edc.id = cp.connection_id
  WHERE edc.is_active = true
  GROUP BY edc.id
`);
8.2 연결 풀 관리
// 외부 DB 연결 풀 생성 및 관리
class ExternalConnectionManager {
  private static pools = new Map<string, Pool>();

  static async getConnection(connectionId: string): Promise<PoolClient> {
    if (!this.pools.has(connectionId)) {
      const config = await this.getConnectionConfig(connectionId);
      this.pools.set(connectionId, new Pool(config));
    }

    return this.pools.get(connectionId)!.connect();
  }

  private static async getConnectionConfig(connectionId: string) {
    const [config] = await DatabaseManager.query(
      `
      SELECT host, port, database, username, password, ssl_config
      FROM external_db_connections 
      WHERE id = $1 AND is_active = true
    `,
      [connectionId]
    );

    return {
      host: config.host,
      port: config.port,
      database: config.database,
      user: config.username,
      password: EncryptUtil.decrypt(config.password),
      ssl: config.ssl_config,
    };
  }
}

🟢 Phase 4: 확장 기능 전환 (129개 호출)

9. adminController.ts (28개 호출)

📊 현재 상태 분석

  • 복잡도: 중간 (컨트롤러 레이어, 다양한 관리 기능)
  • 주요 기능: 관리자 메뉴, 사용자 관리, 권한 관리, 시스템 설정
  • 의존성: user_info, menu_info, auth_groups, company_mng

🔧 전환 전략

9.1 메뉴 관리 API 전환
// 기존 Prisma 코드
export async function getAdminMenus(req: AuthenticatedRequest, res: Response) {
  const menus = await prisma.menu_info.findMany({
    where: {
      is_active: "Y",
      company_code: userCompanyCode,
    },
    include: {
      parent: true,
      children: { where: { is_active: "Y" } },
    },
    orderBy: { sort_order: "asc" },
  });

  res.json({ success: true, data: menus });
}

// 새로운 Raw Query 코드
export async function getAdminMenus(req: AuthenticatedRequest, res: Response) {
  // 계층형 메뉴 구조를 한 번의 쿼리로 조회
  const menus = await DatabaseManager.query(
    `
    WITH RECURSIVE menu_tree AS (
      SELECT 
        m.*,
        0 as level,
        ARRAY[m.sort_order] as path
      FROM menu_info m
      WHERE m.parent_id IS NULL 
        AND m.is_active = 'Y'
        AND m.company_code = $1
      
      UNION ALL
      
      SELECT 
        m.*,
        mt.level + 1,
        mt.path || m.sort_order
      FROM menu_info m
      JOIN menu_tree mt ON m.parent_id = mt.id
      WHERE m.is_active = 'Y'
    )
    SELECT * FROM menu_tree 
    ORDER BY path
  `,
    [userCompanyCode]
  );

  res.json({ success: true, data: menus });
}
9.2 사용자 관리 API 전환
// 기존 Prisma 코드
export async function getUserList(req: AuthenticatedRequest, res: Response) {
  const users = await prisma.user_info.findMany({
    where: { company_code: userCompanyCode },
    include: {
      dept_info: true,
      user_auth: { include: { auth_group: true } },
    },
  });
}

// 새로운 Raw Query 코드
export async function getUserList(req: AuthenticatedRequest, res: Response) {
  const users = await DatabaseManager.query(
    `
    SELECT 
      ui.*,
      di.dept_name,
      json_agg(
        json_build_object(
          'auth_code', ag.auth_code,
          'auth_name', ag.auth_name
        )
      ) FILTER (WHERE ag.auth_code IS NOT NULL) as authorities
    FROM user_info ui
    LEFT JOIN dept_info di ON ui.dept_code = di.dept_code
    LEFT JOIN user_auth ua ON ui.user_id = ua.user_id
    LEFT JOIN auth_group ag ON ua.auth_code = ag.auth_code
    WHERE ui.company_code = $1
    GROUP BY ui.user_id, di.dept_name
    ORDER BY ui.created_date DESC
  `,
    [userCompanyCode]
  );
}
9.3 권한 관리 API 전환
// 복잡한 권한 체크 로직
export async function checkUserPermission(
  req: AuthenticatedRequest,
  res: Response
) {
  const { menuUrl } = req.body;

  const hasPermission = await DatabaseManager.query(
    `
    SELECT EXISTS (
      SELECT 1 
      FROM user_auth ua
      JOIN auth_group ag ON ua.auth_code = ag.auth_code
      JOIN menu_auth_group mag ON ag.auth_code = mag.auth_code
      JOIN menu_info mi ON mag.menu_id = mi.id
      WHERE ua.user_id = $1 
        AND mi.url = $2
        AND ua.is_active = 'Y'
        AND ag.is_active = 'Y'
        AND mi.is_active = 'Y'
    ) as has_permission
  `,
    [req.user.userId, menuUrl]
  );

  res.json({ success: true, hasPermission: hasPermission[0].has_permission });
}

🧪 테스트 전략

  1. API 응답 테스트: 기존 API와 동일한 응답 구조 확인
  2. 권한 테스트: 다양한 사용자 권한 시나리오
  3. 계층 구조 테스트: 메뉴 트리 구조 정확성
  4. 성능 테스트: 복잡한 조인 쿼리 성능

10. componentStandardService.ts (16개 호출)

📊 현재 상태 분석

  • 복잡도: 중간 (컴포넌트 표준 관리)
  • 주요 기능: UI 컴포넌트 표준 정의, 템플릿 관리
  • 의존성: component_standards, ui_templates

🔧 전환 전략

10.1 컴포넌트 표준 조회
// 기존 Prisma 코드
const components = await prisma.component_standards.findMany({
  where: { category: category },
  include: {
    templates: { where: { is_active: true } },
    properties: true,
  },
});

// 새로운 Raw Query 코드
const components = await DatabaseManager.query(
  `
  SELECT 
    cs.*,
    json_agg(
      DISTINCT jsonb_build_object(
        'id', ut.id,
        'template_name', ut.template_name,
        'template_config', ut.template_config
      )
    ) FILTER (WHERE ut.id IS NOT NULL) as templates,
    json_agg(
      DISTINCT jsonb_build_object(
        'property_name', cp.property_name,
        'property_type', cp.property_type,
        'default_value', cp.default_value
      )
    ) FILTER (WHERE cp.id IS NOT NULL) as properties
  FROM component_standards cs
  LEFT JOIN ui_templates ut ON cs.id = ut.component_id AND ut.is_active = true
  LEFT JOIN component_properties cp ON cs.id = cp.component_id
  WHERE cs.category = $1
  GROUP BY cs.id
`,
  [category]
);

11. commonCodeService.ts (15개 호출)

📊 현재 상태 분석

  • 복잡도: 중간 (코드 관리, 계층 구조)
  • 주요 기능: 공통 코드 관리, 코드 카테고리 관리
  • 의존성: code_info, code_category

🔧 전환 전략

11.1 계층형 코드 구조 처리
// 기존 Prisma 코드
const codes = await prisma.code_info.findMany({
  where: { category_code: categoryCode },
  include: {
    parent: true,
    children: { where: { is_active: "Y" } },
  },
});

// 새로운 Raw Query 코드 (재귀 CTE 사용)
const codes = await DatabaseManager.query(
  `
  WITH RECURSIVE code_tree AS (
    SELECT 
      ci.*,
      0 as level,
      CAST(ci.sort_order as TEXT) as path
    FROM code_info ci
    WHERE ci.parent_code IS NULL 
      AND ci.category_code = $1
      AND ci.is_active = 'Y'
    
    UNION ALL
    
    SELECT 
      ci.*,
      ct.level + 1,
      ct.path || '.' || ci.sort_order
    FROM code_info ci
    JOIN code_tree ct ON ci.parent_code = ct.code
    WHERE ci.is_active = 'Y'
  )
  SELECT * FROM code_tree 
  ORDER BY path
`,
  [categoryCode]
);

12. batchService.ts (16개 호출)

📊 현재 상태 분석

  • 복잡도: 중간-높음 (배치 작업, 스케줄링)
  • 주요 기능: 배치 작업 관리, 실행 이력, 스케줄링
  • 의존성: batch_configs, batch_execution_logs

🔧 전환 전략

12.1 배치 실행 이력 관리
// 기존 Prisma 코드
const batchHistory = await prisma.batch_execution_logs.findMany({
  where: { batch_config_id: configId },
  include: { batch_config: true },
  orderBy: { created_at: "desc" },
  take: 50,
});

// 새로운 Raw Query 코드
const batchHistory = await DatabaseManager.query(
  `
  SELECT 
    bel.*,
    bc.batch_name,
    bc.description as batch_description
  FROM batch_execution_logs bel
  JOIN batch_configs bc ON bel.batch_config_id = bc.id
  WHERE bel.batch_config_id = $1
  ORDER BY bel.created_at DESC
  LIMIT 50
`,
  [configId]
);
12.2 배치 상태 업데이트
// 트랜잭션을 사용한 배치 상태 관리
async updateBatchStatus(batchId: number, status: string, result?: any) {
  await DatabaseManager.transaction(async (client) => {
    // 실행 로그 업데이트
    await client.query(`
      UPDATE batch_execution_logs
      SET status = $1,
          result = $2,
          completed_at = NOW(),
          updated_at = NOW()
      WHERE id = $3
    `, [status, result, batchId]);

    // 배치 설정의 마지막 실행 시간 업데이트
    await client.query(`
      UPDATE batch_configs
      SET last_executed_at = NOW(),
          last_status = $1,
          updated_at = NOW()
      WHERE id = (
        SELECT batch_config_id
        FROM batch_execution_logs
        WHERE id = $2
      )
    `, [status, batchId]);
  });
}

📋 나머지 파일들 요약 전환 계획

Phase 2 나머지 파일들 (6개 호출)

  • dataflowControlService.ts (6개): 제어 로직, 조건부 실행
  • ddlExecutionService.ts (6개): DDL 실행, 스키마 변경
  • authService.ts (5개): 사용자 인증, 토큰 관리
  • multiConnectionQueryService.ts (4개): 다중 DB 연결

Phase 3 나머지 파일들 (121개 호출)

  • dataflowDiagramService.ts (12개): 다이어그램 관리, JSON 처리
  • collectionService.ts (11개): 컬렉션 관리
  • layoutService.ts (10개): 레이아웃 관리
  • dbTypeCategoryService.ts (10개): DB 타입 분류
  • templateStandardService.ts (9개): 템플릿 표준
  • ddlAuditLogger.ts (8개): DDL 감사 로그
  • externalCallConfigService.ts (8개): 외부 호출 설정
  • batchExternalDbService.ts (8개): 배치 외부DB
  • batchExecutionLogService.ts (7개): 배치 실행 로그
  • eventTriggerService.ts (6개): 이벤트 트리거
  • enhancedDynamicFormService.ts (6개): 확장 동적 폼
  • entityJoinService.ts (5개): 엔티티 조인
  • dataMappingService.ts (5개): 데이터 매핑
  • batchManagementService.ts (5개): 배치 관리
  • batchSchedulerService.ts (4개): 배치 스케줄러
  • dataService.ts (4개): 데이터 서비스
  • adminService.ts (3개): 관리자 서비스
  • referenceCacheService.ts (3개): 참조 캐시

Phase 4 나머지 파일들 (101개 호출)

  • webTypeStandardController.ts (11개): 웹타입 표준 컨트롤러
  • fileController.ts (11개): 파일 업로드/다운로드 컨트롤러
  • buttonActionStandardController.ts (11개): 버튼 액션 표준
  • entityReferenceController.ts (4개): 엔티티 참조 컨트롤러
  • database.ts (4개): 데이터베이스 설정
  • dataflowExecutionController.ts (3개): 데이터플로우 실행
  • screenFileController.ts (2개): 화면 파일 컨트롤러
  • ddlRoutes.ts (2개): DDL 라우트
  • companyManagementRoutes.ts (2개): 회사 관리 라우트

🔗 파일 간 의존성 분석

1. 핵심 의존성 체인

DatabaseManager (기반)
  ↓
QueryBuilder (쿼리 생성)
  ↓
Services (비즈니스 로직)
  ↓
Controllers (API 엔드포인트)
  ↓
Routes (라우팅)

2. 서비스 간 의존성

tableManagementService
  ↓ (테이블 메타데이터)
screenManagementService
  ↓ (화면 정의)
dynamicFormService
  ↓ (폼 데이터)
dataflowControlService

3. 전환 순서 (의존성 고려)

  1. 기반 구조: DatabaseManager, QueryBuilder
  2. 메타데이터 서비스: tableManagementService
  3. 핵심 비즈니스: screenManagementService, dataflowService
  4. 폼 처리: dynamicFormService
  5. 외부 연동: externalDbConnectionService
  6. 관리 기능: adminService, commonCodeService
  7. 배치 시스템: batchService 계열
  8. 컨트롤러: adminController 등
  9. 라우트: 각종 라우트 파일들

전환 가속화 전략

1. 병렬 전환 가능 그룹

그룹 A (독립적): authService, adminService, commonCodeService
그룹 B (배치 관련): batchService, batchSchedulerService, batchExecutionLogService
그룹 C (컨트롤러): adminController, fileController, webTypeStandardController
그룹 D (표준 관리): componentStandardService, templateStandardService

2. 공통 패턴 템플릿 활용

// 표준 CRUD 템플릿
class StandardCRUDTemplate {
  static async create(tableName: string, data: any) {
    const { query, params } = QueryBuilder.insert(tableName, data, {
      returning: ["*"],
    });
    return await DatabaseManager.query(query, params);
  }

  static async findMany(tableName: string, options: any) {
    const { query, params } = QueryBuilder.select(tableName, options);
    return await DatabaseManager.query(query, params);
  }

  // ... 기타 표준 메서드들
}

3. 자동화 도구 활용

// Prisma → Raw Query 자동 변환 도구
class PrismaToRawConverter {
  static convertFindMany(prismaCall: string): string {
    // Prisma findMany 호출을 Raw Query로 자동 변환
  }

  static convertCreate(prismaCall: string): string {
    // Prisma create 호출을 Raw Query로 자동 변환
  }
}

🧪 통합 테스트 전략

1. 파일별 테스트 매트릭스

파일명 단위테스트 통합테스트 성능테스트 E2E테스트
screenManagementService
tableManagementService
dataflowService
adminController

2. 회귀 테스트 자동화

// 기존 Prisma vs 새로운 Raw Query 결과 비교
describe("Migration Regression Tests", () => {
  test("screenManagementService.getScreens should return identical results", async () => {
    const prismaResult = await oldScreenService.getScreens(params);
    const rawQueryResult = await newScreenService.getScreens(params);

    expect(normalizeResult(rawQueryResult)).toEqual(
      normalizeResult(prismaResult)
    );
  });
});

3. 성능 벤치마크

// 성능 비교 테스트
describe("Performance Benchmarks", () => {
  test("Complex query performance comparison", async () => {
    const iterations = 1000;

    const prismaTime = await measureTime(
      () => prismaService.complexQuery(params),
      iterations
    );

    const rawQueryTime = await measureTime(
      () => rawQueryService.complexQuery(params),
      iterations
    );

    expect(rawQueryTime).toBeLessThanOrEqual(prismaTime * 1.1); // 10% 허용
  });
});

🔧 공통 전환 패턴

1. 기본 CRUD 패턴

// CREATE
const { query, params } = QueryBuilder.insert(tableName, data, {
  returning: ["*"],
});
const [result] = await DatabaseManager.query(query, params);

// READ
const { query, params } = QueryBuilder.select(tableName, {
  where,
  orderBy,
  limit,
});
const results = await DatabaseManager.query(query, params);

// UPDATE
const { query, params } = QueryBuilder.update(tableName, data, where);
const [result] = await DatabaseManager.query(query, params);

// DELETE
const { query, params } = QueryBuilder.delete(tableName, where);
const results = await DatabaseManager.query(query, params);

2. 트랜잭션 패턴

await DatabaseManager.transaction(async (client) => {
  const result1 = await client.query(query1, params1);
  const result2 = await client.query(query2, params2);
  return { result1, result2 };
});

3. 동적 쿼리 패턴

const tableName = DatabaseValidator.sanitizeTableName(userInput);
const columnName = DatabaseValidator.sanitizeColumnName(userInput);
const query = `SELECT ${columnName} FROM ${tableName} WHERE id = $1`;
const result = await DatabaseManager.query(query, [id]);

📋 전환 체크리스트

각 파일별 필수 확인사항

  • 모든 Prisma 호출 식별 및 변환
  • 타입 변환 (Date, JSON, BigInt) 처리
  • NULL 값 처리 일관성
  • 트랜잭션 경계 유지
  • 에러 처리 로직 보존
  • 성능 최적화 (인덱스 힌트 등)
  • 단위 테스트 작성
  • 통합 테스트 실행
  • 기능 동작 검증
  • 성능 비교 테스트

공통 주의사항

  1. SQL 인젝션 방지: 모든 동적 쿼리에 파라미터 바인딩 사용
  2. 타입 안전성: TypeScript 타입 정의 유지
  3. 에러 처리: Prisma 에러를 적절한 HTTP 상태코드로 변환
  4. 로깅: 쿼리 실행 로그 및 성능 모니터링
  5. 백워드 호환성: API 응답 형식 유지

🎯 성공 기준

기능적 요구사항

  • 모든 기존 API 엔드포인트 정상 동작
  • 데이터 일관성 유지
  • 트랜잭션 무결성 보장
  • 에러 처리 동일성

성능 요구사항

  • 응답 시간 기존 대비 ±10% 이내
  • 메모리 사용량 최적화
  • 동시 접속 처리 능력 유지

품질 요구사항

  • 코드 커버리지 90% 이상
  • 타입 안전성 보장
  • 보안 검증 통과
  • 문서화 완료

이 상세 계획을 통해 각 파일별로 체계적이고 안전한 마이그레이션을 수행할 수 있습니다.