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

1217 lines
32 KiB
Markdown

# 📋 파일별 상세 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 작업 전환
```typescript
// 기존 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 복잡한 조인 쿼리 전환
```typescript
// 기존 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 데이터 처리 전환
```typescript
// 기존 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 트랜잭션 처리 전환
```typescript
// 기존 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 사용 중)
```typescript
// 현재 코드 (이미 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 동적 테이블 데이터 조회
```typescript
// 기존 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 작업 전환
```typescript
// 기존 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 관계 생성 로직
```typescript
// 기존 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 재귀 쿼리 전환
```typescript
// 기존 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 배치 설정 관리
```typescript
// 기존 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 로직 전환
```typescript
// 기존 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 타입 변환 로직 강화
```typescript
// 기존 타입 변환 (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 동적 검증 로직
```typescript
// 기존 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 연결 설정 관리
```typescript
// 기존 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 연결 풀 관리
```typescript
// 외부 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 전환
```typescript
// 기존 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 전환
```typescript
// 기존 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 전환
```typescript
// 복잡한 권한 체크 로직
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 컴포넌트 표준 조회
```typescript
// 기존 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 계층형 코드 구조 처리
```typescript
// 기존 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 배치 실행 이력 관리
```typescript
// 기존 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 배치 상태 업데이트
```typescript
// 트랜잭션을 사용한 배치 상태 관리
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. 공통 패턴 템플릿 활용
```typescript
// 표준 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. 자동화 도구 활용
```typescript
// 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. 회귀 테스트 자동화
```typescript
// 기존 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. 성능 벤치마크
```typescript
// 성능 비교 테스트
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 패턴
```typescript
// 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. 트랜잭션 패턴
```typescript
await DatabaseManager.transaction(async (client) => {
const result1 = await client.query(query1, params1);
const result2 = await client.query(query2, params2);
return { result1, result2 };
});
```
### 3. 동적 쿼리 패턴
```typescript
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% 이상
- [ ] 타입 안전성 보장
- [ ] 보안 검증 통과
- [ ] 문서화 완료
이 상세 계획을 통해 각 파일별로 체계적이고 안전한 마이그레이션을 수행할 수 있습니다.