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>
32 KiB
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;
});
🧪 테스트 전략
- 단위 테스트: 각 메서드별 입출력 검증
- 통합 테스트: 화면 생성 → 조회 → 수정 → 삭제 전체 플로우
- JSON 데이터 테스트: 복잡한 레이아웃 데이터 저장/복원
- 성능 테스트: 대량 화면 데이터 처리 성능 비교
⚠️ 주의사항
- 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);
🧪 테스트 전략
- 메타데이터 테스트: information_schema 조회 결과 일치성
- 동적 쿼리 테스트: 다양한 테이블명/컬럼명으로 안전성 검증
- DDL 테스트: 테이블 생성/수정/삭제 기능
- 캐시 테스트: 메타데이터 캐싱 동작 검증
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);
🧪 테스트 전략
- 관계 생성 테스트: 다양한 테이블 관계 패턴
- 중복 검증 테스트: 동일 관계 생성 방지
- 다이어그램 테스트: 복잡한 관계도 생성/조회
🟡 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
`);
🧪 테스트 전략
- 재귀 쿼리 테스트: 깊은 계층 구조 처리
- 다국어 테스트: 다양한 언어 코드 처리
- 성능 테스트: 대량 번역 데이터 처리
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 };
}
🧪 테스트 전략
- UPSERT 테스트: 신규 생성 vs 기존 업데이트 시나리오
- 타입 변환 테스트: 다양한 PostgreSQL 타입 변환
- 검증 테스트: 필수 필드, 타입 검증, 길이 제한
- 동적 테이블 테스트: 런타임에 생성된 테이블 처리
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 });
}
🧪 테스트 전략
- API 응답 테스트: 기존 API와 동일한 응답 구조 확인
- 권한 테스트: 다양한 사용자 권한 시나리오
- 계층 구조 테스트: 메뉴 트리 구조 정확성
- 성능 테스트: 복잡한 조인 쿼리 성능
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. 전환 순서 (의존성 고려)
- 기반 구조: DatabaseManager, QueryBuilder
- 메타데이터 서비스: tableManagementService
- 핵심 비즈니스: screenManagementService, dataflowService
- 폼 처리: dynamicFormService
- 외부 연동: externalDbConnectionService
- 관리 기능: adminService, commonCodeService
- 배치 시스템: batchService 계열
- 컨트롤러: adminController 등
- 라우트: 각종 라우트 파일들
⚡ 전환 가속화 전략
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 값 처리 일관성
- 트랜잭션 경계 유지
- 에러 처리 로직 보존
- 성능 최적화 (인덱스 힌트 등)
- 단위 테스트 작성
- 통합 테스트 실행
- 기능 동작 검증
- 성능 비교 테스트
공통 주의사항
- SQL 인젝션 방지: 모든 동적 쿼리에 파라미터 바인딩 사용
- 타입 안전성: TypeScript 타입 정의 유지
- 에러 처리: Prisma 에러를 적절한 HTTP 상태코드로 변환
- 로깅: 쿼리 실행 로그 및 성능 모니터링
- 백워드 호환성: API 응답 형식 유지
🎯 성공 기준
기능적 요구사항
- 모든 기존 API 엔드포인트 정상 동작
- 데이터 일관성 유지
- 트랜잭션 무결성 보장
- 에러 처리 동일성
성능 요구사항
- 응답 시간 기존 대비 ±10% 이내
- 메모리 사용량 최적화
- 동시 접속 처리 능력 유지
품질 요구사항
- 코드 커버리지 90% 이상
- 타입 안전성 보장
- 보안 검증 통과
- 문서화 완료
이 상세 계획을 통해 각 파일별로 체계적이고 안전한 마이그레이션을 수행할 수 있습니다.