ERP-node/PRISMA_TO_RAW_QUERY_MIGRATI...

1316 lines
44 KiB
Markdown
Raw Normal View History

2025-09-29 17:19:31 +09:00
# 🚀 Prisma → Raw Query 완전 전환 계획서
## 📋 프로젝트 개요
### 🎯 목적
현재 Node.js 백엔드에서 Prisma ORM을 완전히 제거하고 Raw Query 방식으로 전환하여 **완전 동적 테이블 생성 및 관리 시스템**을 구축합니다.
### 🔍 현재 상황 분석
- **총 52개 파일**에서 Prisma 사용
- **490개의 Prisma 호출** (ORM + Raw Query 혼재)
2025-09-29 17:19:31 +09:00
- **150개 이상의 테이블** 정의 (schema.prisma)
- **복잡한 트랜잭션 및 동적 쿼리** 다수 존재
---
## 📊 Prisma 사용 현황 분석
**총 42개 파일에서 444개의 Prisma 호출 발견** ⚡ (Scripts 제외)
2025-09-29 17:19:31 +09:00
### 1. **Prisma 사용 파일 분류**
#### 🔴 **High Priority (핵심 서비스) - 107개 호출**
2025-09-29 17:19:31 +09:00
```
backend-node/src/services/
├── screenManagementService.ts # 화면 관리 (46개 호출) ⭐ 최우선
├── tableManagementService.ts # 테이블 관리 (35개 호출) ⭐ 최우선
2025-10-01 10:03:41 +09:00
├── dataflowService.ts # 데이터플로우 (0개 호출) ✅ 전환 완료
├── dynamicFormService.ts # 동적 폼 (0개 호출) ✅ 전환 완료
├── externalDbConnectionService.ts # 외부DB (0개 호출) ✅ 전환 완료
├── dataflowControlService.ts # 제어관리 (0개 호출) ✅ 전환 완료
├── multilangService.ts # 다국어 (0개 호출) ✅ 전환 완료
├── ddlExecutionService.ts # DDL 실행 (6개 호출)
├── authService.ts # 인증 (5개 호출)
└── multiConnectionQueryService.ts # 다중 연결 (4개 호출)
2025-09-29 17:19:31 +09:00
```
#### 🟡 **Medium Priority (관리 기능) - 142개 호출**
2025-09-29 17:19:31 +09:00
```
backend-node/src/services/
├── multilangService.ts # 다국어 (25개 호출)
├── batchService.ts # 배치 (16개 호출)
├── componentStandardService.ts # 컴포넌트 (16개 호출)
├── commonCodeService.ts # 공통코드 (15개 호출)
├── dataflowDiagramService.ts # 데이터플로우 다이어그램 (12개 호출) ⭐ 신규 발견
├── collectionService.ts # 컬렉션 (11개 호출)
├── layoutService.ts # 레이아웃 (10개 호출)
├── dbTypeCategoryService.ts # DB 타입 카테고리 (10개 호출) ⭐ 신규 발견
├── templateStandardService.ts # 템플릿 (9개 호출)
├── ddlAuditLogger.ts # DDL 감사 로그 (8개 호출) ⭐ 신규 발견
├── externalCallConfigService.ts # 외부 호출 설정 (8개 호출) ⭐ 신규 발견
├── batchExternalDbService.ts # 배치 외부DB (8개 호출) ⭐ 신규 발견
├── batchExecutionLogService.ts # 배치 실행 로그 (7개 호출) ⭐ 신규 발견
├── eventTriggerService.ts # 이벤트 (6개 호출)
├── enhancedDynamicFormService.ts # 확장 동적 폼 (6개 호출) ⭐ 신규 발견
├── entityJoinService.ts # 엔티티 조인 (5개 호출) ⭐ 신규 발견
├── dataMappingService.ts # 데이터 매핑 (5개 호출) ⭐ 신규 발견
├── batchManagementService.ts # 배치 관리 (5개 호출) ⭐ 신규 발견
├── batchSchedulerService.ts # 배치 스케줄러 (4개 호출) ⭐ 신규 발견
├── dataService.ts # 데이터 서비스 (4개 호출) ⭐ 신규 발견
2025-09-29 17:19:31 +09:00
├── adminService.ts # 관리자 (3개 호출)
└── referenceCacheService.ts # 캐시 (3개 호출)
2025-09-29 17:19:31 +09:00
```
#### 🟢 **Low Priority (컨트롤러 & 라우트) - 188개 호출**
2025-09-29 17:19:31 +09:00
```
backend-node/src/controllers/
├── adminController.ts # 관리자 컨트롤러 (28개 호출) ⭐ 신규 발견
├── webTypeStandardController.ts # 웹타입 표준 (11개 호출) ⭐ 신규 발견
├── fileController.ts # 파일 컨트롤러 (11개 호출) ⭐ 신규 발견
├── buttonActionStandardController.ts # 버튼 액션 표준 (11개 호출) ⭐ 신규 발견
├── entityReferenceController.ts # 엔티티 참조 (4개 호출) ⭐ 신규 발견
├── dataflowExecutionController.ts # 데이터플로우 실행 (3개 호출) ⭐ 신규 발견
└── screenFileController.ts # 화면 파일 (2개 호출) ⭐ 신규 발견
2025-09-29 17:19:31 +09:00
backend-node/src/routes/
├── ddlRoutes.ts # DDL 라우트 (2개 호출) ⭐ 신규 발견
└── companyManagementRoutes.ts # 회사 관리 라우트 (2개 호출) ⭐ 신규 발견
backend-node/src/config/
└── database.ts # 데이터베이스 설정 (4개 호출)
2025-09-29 17:19:31 +09:00
#### 🗑️ **삭제 예정 Scripts - 60개 호출** ⚠️ 사용하지 않음
2025-09-29 17:19:31 +09:00
```
backend-node/scripts/ (삭제 예정)
├── install-dataflow-indexes.js # 인덱스 설치 (10개 호출) 🗑️ 삭제
├── add-missing-columns.js # 컬럼 추가 (8개 호출) 🗑️ 삭제
├── test-template-creation.js # 템플릿 테스트 (6개 호출) 🗑️ 삭제
├── create-component-table.js # 컴포넌트 테이블 생성 (5개 호출) 🗑️ 삭제
├── seed-ui-components.js # UI 컴포넌트 시드 (3개 호출) 🗑️ 삭제
├── seed-templates.js # 템플릿 시드 (3개 호출) 🗑️ 삭제
├── init-layout-standards.js # 레이아웃 표준 초기화 (3개 호출) 🗑️ 삭제
├── add-data-mapping-column.js # 데이터 매핑 컬럼 추가 (3개 호출) 🗑️ 삭제
├── add-button-webtype.js # 버튼 웹타입 추가 (3개 호출) 🗑️ 삭제
└── list-components.js # 컴포넌트 목록 (2개 호출) 🗑️ 삭제
2025-09-29 17:19:31 +09:00
backend-node/ (루트)
└── clean-screen-tables.js # 화면 테이블 정리 (7개 호출) 🗑️ 삭제
2025-09-29 17:19:31 +09:00
````
2025-09-29 17:19:31 +09:00
**⚠️ 삭제 계획**: 이 스크립트들은 개발/배포 도구로 운영 시스템에서 사용하지 않으므로 마이그레이션 전에 삭제 예정
### 2. **복잡도별 분류**
2025-09-29 17:19:31 +09:00
#### 🔥 **매우 복잡 (트랜잭션 + 동적 쿼리) - 최우선 처리**
- `screenManagementService.ts` (46개) - 화면 정의 관리, JSON 처리
- `tableManagementService.ts` (35개) - 테이블 메타데이터 관리, DDL 실행
2025-10-01 10:03:41 +09:00
- `dataflowService.ts` (0개) - ✅ **전환 완료** (Phase 2.3)
- `dynamicFormService.ts` (0개) - ✅ **전환 완료** (Phase 2.4)
- `externalDbConnectionService.ts` (0개) - ✅ **전환 완료** (Phase 2.5)
- `dataflowControlService.ts` (0개) - ✅ **전환 완료** (Phase 2.6)
- `enhancedDataflowControlService.ts` (0개) - 다중 연결 제어 (Raw Query만 사용)
- `multiConnectionQueryService.ts` (4개) - 외부 DB 연결
#### 🟠 **복잡 (Raw Query 혼재) - 2순위**
- `multilangService.ts` (0개) - ✅ **전환 완료** (Phase 3.1)
- `batchService.ts` (0개) - ✅ **전환 완료** (Phase 3.2)
- `componentStandardService.ts` (16개) - 컴포넌트 표준 관리
- `commonCodeService.ts` (15개) - 코드 관리, 계층 구조
- `dataflowDiagramService.ts` (12개) - 다이어그램 관리 ⭐ 신규 발견
- `collectionService.ts` (11개) - 컬렉션 관리
- `layoutService.ts` (10개) - 레이아웃 관리
- `dbTypeCategoryService.ts` (10개) - DB 타입 분류 ⭐ 신규 발견
- `templateStandardService.ts` (9개) - 템플릿 표준
- `eventTriggerService.ts` (6개) - JSON 검색 쿼리
#### 🟡 **중간 (단순 CRUD) - 3순위**
- `ddlAuditLogger.ts` (8개) - DDL 감사 로그 ⭐ 신규 발견
- `externalCallConfigService.ts` (8개) - 외부 호출 설정 ⭐ 신규 발견
- `batchExternalDbService.ts` (8개) - 배치 외부DB ⭐ 신규 발견
- `batchExecutionLogService.ts` (7개) - 배치 실행 로그 ⭐ 신규 발견
- `enhancedDynamicFormService.ts` (6개) - 확장 동적 폼 ⭐ 신규 발견
- `ddlExecutionService.ts` (6개) - DDL 실행
- `entityJoinService.ts` (5개) - 엔티티 조인 ⭐ 신규 발견
- `dataMappingService.ts` (5개) - 데이터 매핑 ⭐ 신규 발견
- `batchManagementService.ts` (5개) - 배치 관리 ⭐ 신규 발견
- `authService.ts` (5개) - 사용자 인증
- `batchSchedulerService.ts` (4개) - 배치 스케줄러 ⭐ 신규 발견
- `dataService.ts` (4개) - 데이터 서비스 ⭐ 신규 발견
- `adminService.ts` (3개) - 관리자 메뉴
- `referenceCacheService.ts` (3개) - 캐시 관리
#### 🟢 **단순 (컨트롤러 레이어) - 4순위**
- `adminController.ts` (28개) - 관리자 컨트롤러 ⭐ 신규 발견
- `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개) - 회사 관리 라우트 ⭐ 신규 발견
#### 🗑️ **삭제 예정 Scripts (마이그레이션 대상 아님)**
- `install-dataflow-indexes.js` (10개) - 인덱스 설치 스크립트 🗑️
- `add-missing-columns.js` (8개) - 컬럼 추가 스크립트 🗑️
- `clean-screen-tables.js` (7개) - 테이블 정리 스크립트 🗑️
- `test-template-creation.js` (6개) - 템플릿 테스트 스크립트 🗑️
- `create-component-table.js` (5개) - 컴포넌트 테이블 생성 🗑️
- 기타 시드 스크립트들 (14개) - 개발용 데이터 시드 🗑️
**⚠️ 중요**: 이 스크립트들은 사용하지 않으므로 마이그레이션 전에 삭제하여 작업량을 60개 호출만큼 줄일 수 있습니다.
2025-09-29 17:19:31 +09:00
---
## 🏗️ Raw Query 아키텍처 설계
### 1. **새로운 데이터베이스 매니저**
```typescript
// config/databaseManager.ts
import { Pool, PoolClient } from "pg";
export class DatabaseManager {
private static pool: Pool;
static initialize() {
this.pool = new Pool({
host: process.env.DB_HOST,
port: parseInt(process.env.DB_PORT || "5432"),
database: process.env.DB_NAME,
user: process.env.DB_USER,
password: process.env.DB_PASSWORD,
max: 20,
idleTimeoutMillis: 30000,
connectionTimeoutMillis: 2000,
});
}
// 기본 쿼리 실행
static async query(text: string, params?: any[]): Promise<any[]> {
const client = await this.pool.connect();
try {
const result = await client.query(text, params);
return result.rows;
} finally {
client.release();
}
}
// 트랜잭션 실행
static async transaction<T>(
callback: (client: PoolClient) => Promise<T>
): Promise<T> {
const client = await this.pool.connect();
try {
await client.query("BEGIN");
const result = await callback(client);
await client.query("COMMIT");
return result;
} catch (error) {
await client.query("ROLLBACK");
throw error;
} finally {
client.release();
}
}
// 연결 종료
static async close() {
await this.pool.end();
}
}
````
2025-09-29 17:19:31 +09:00
### 2. **동적 쿼리 빌더**
```typescript
// utils/queryBuilder.ts
export class QueryBuilder {
// SELECT 쿼리 빌더
static select(
tableName: string,
options: {
columns?: string[];
where?: Record<string, any>;
orderBy?: string;
limit?: number;
offset?: number;
joins?: Array<{
type: "INNER" | "LEFT" | "RIGHT";
table: string;
on: string;
}>;
} = {}
) {
const {
columns = ["*"],
where = {},
orderBy,
limit,
offset,
joins = [],
} = options;
let query = `SELECT ${columns.join(", ")} FROM ${tableName}`;
const params: any[] = [];
let paramIndex = 1;
// JOIN 처리
joins.forEach((join) => {
query += ` ${join.type} JOIN ${join.table} ON ${join.on}`;
});
// WHERE 조건
if (Object.keys(where).length > 0) {
const whereClause = Object.keys(where)
.map((key) => `${key} = $${paramIndex++}`)
.join(" AND ");
query += ` WHERE ${whereClause}`;
params.push(...Object.values(where));
}
// ORDER BY
if (orderBy) {
query += ` ORDER BY ${orderBy}`;
}
// LIMIT/OFFSET
if (limit) {
query += ` LIMIT $${paramIndex++}`;
params.push(limit);
}
if (offset) {
query += ` OFFSET $${paramIndex++}`;
params.push(offset);
}
return { query, params };
}
// INSERT 쿼리 빌더
static insert(
tableName: string,
data: Record<string, any>,
options: {
returning?: string[];
onConflict?: {
columns: string[];
action: "DO NOTHING" | "DO UPDATE";
updateSet?: string[];
};
} = {}
) {
const columns = Object.keys(data);
const values = Object.values(data);
const placeholders = values.map((_, index) => `$${index + 1}`).join(", ");
let query = `INSERT INTO ${tableName} (${columns.join(
", "
)}) VALUES (${placeholders})`;
// ON CONFLICT 처리 (UPSERT)
if (options.onConflict) {
const {
columns: conflictColumns,
action,
updateSet,
} = options.onConflict;
query += ` ON CONFLICT (${conflictColumns.join(", ")}) ${action}`;
if (action === "DO UPDATE" && updateSet) {
const setClause = updateSet
.map((col) => `${col} = EXCLUDED.${col}`)
.join(", ");
query += ` SET ${setClause}`;
}
}
// RETURNING 처리
if (options.returning) {
query += ` RETURNING ${options.returning.join(", ")}`;
}
return { query, params: values };
}
// UPDATE 쿼리 빌더
static update(
tableName: string,
data: Record<string, any>,
where: Record<string, any>
) {
const setClause = Object.keys(data)
.map((key, index) => `${key} = $${index + 1}`)
.join(", ");
const whereClause = Object.keys(where)
.map((key, index) => `${key} = $${Object.keys(data).length + index + 1}`)
.join(" AND ");
const query = `UPDATE ${tableName} SET ${setClause} WHERE ${whereClause} RETURNING *`;
const params = [...Object.values(data), ...Object.values(where)];
return { query, params };
}
// DELETE 쿼리 빌더
static delete(tableName: string, where: Record<string, any>) {
const whereClause = Object.keys(where)
.map((key, index) => `${key} = $${index + 1}`)
.join(" AND ");
const query = `DELETE FROM ${tableName} WHERE ${whereClause} RETURNING *`;
const params = Object.values(where);
return { query, params };
}
}
```
### 3. **타입 안전성 보장**
```typescript
// types/database.ts
export interface QueryResult<T = any> {
rows: T[];
rowCount: number;
command: string;
}
export interface TableSchema {
tableName: string;
columns: ColumnDefinition[];
}
export interface ColumnDefinition {
name: string;
type: string;
nullable?: boolean;
defaultValue?: string;
isPrimaryKey?: boolean;
}
// 런타임 검증
export class DatabaseValidator {
static validateTableName(tableName: string): boolean {
return /^[a-zA-Z_][a-zA-Z0-9_]*$/.test(tableName) && tableName.length <= 63;
}
static validateColumnName(columnName: string): boolean {
return (
/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(columnName) && columnName.length <= 63
);
}
static sanitizeInput(input: any): any {
if (typeof input === "string") {
return input.replace(/[';--]/g, "");
}
return input;
}
static validateWhereClause(where: Record<string, any>): boolean {
return Object.keys(where).every((key) => this.validateColumnName(key));
}
}
```
---
## 📅 단계별 마이그레이션 계획
### **Phase 1: 기반 구조 구축 (1주)**
#### 1.1 새로운 데이터베이스 아키텍처 구현
- [ ] `DatabaseManager` 클래스 구현
- [ ] `QueryBuilder` 유틸리티 구현
- [ ] 타입 정의 및 검증 로직 구현
- [ ] 연결 풀 및 트랜잭션 관리
#### 1.2 테스트 환경 구축
- [ ] 단위 테스트 작성
- [ ] 통합 테스트 환경 구성
- [ ] 성능 벤치마크 도구 준비
### **Phase 2: 핵심 서비스 전환 (3주) - 최우선**
2025-09-29 17:19:31 +09:00
#### 2.1 화면 관리 서비스 전환 (우선순위 1) - 46개 호출
2025-09-29 17:19:31 +09:00
```typescript
// 기존 Prisma 코드 (복잡한 JSON 처리)
const screenData = await prisma.screen_definitions.findMany({
where: {
company_code: companyCode,
screen_config: { path: ["type"], equals: "form" },
},
include: { screen_components: true },
2025-09-29 17:19:31 +09:00
});
// 새로운 Raw Query 코드
const { query, params } = QueryBuilder.select("screen_definitions", {
columns: ["*", "screen_config::jsonb"],
where: {
company_code: companyCode,
"screen_config->>'type'": "form",
},
joins: [
{
type: "LEFT",
table: "screen_components",
on: "screen_definitions.id = screen_components.screen_id",
},
],
2025-09-29 17:19:31 +09:00
});
const screenData = await DatabaseManager.query(query, params);
2025-09-29 17:19:31 +09:00
```
#### 2.2 테이블 관리 서비스 전환 (우선순위 2) - 35개 호출
- [ ] 동적 테이블 생성/삭제 로직 전환
- [ ] 메타데이터 관리 시스템 개선
- [ ] DDL 실행 트랜잭션 처리
- [ ] 컬럼 타입 변환 로직 최적화
#### 2.3 데이터플로우 서비스 전환 (우선순위 3) - 31개 호출 ⭐ 신규 발견
- [ ] 복잡한 관계 관리 로직 전환
- [ ] 트랜잭션 기반 데이터 이동 처리
- [ ] JSON 기반 설정 관리 개선
- [ ] 다중 테이블 조인 최적화
#### 2.4 동적 폼 서비스 전환 (우선순위 4) - 15개 호출
2025-09-29 17:19:31 +09:00
- [ ] UPSERT 로직 Raw Query로 전환
- [ ] 동적 테이블 처리 로직 개선
- [ ] 트랜잭션 처리 최적화
#### 2.5 외부 DB 연결 서비스 전환 (우선순위 5) - 15개 호출
2025-09-29 17:19:31 +09:00
- [ ] 다중 DB 연결 관리 로직
- [ ] 연결 풀 관리 시스템
- [ ] 외부 DB 스키마 동기화
2025-09-29 17:19:31 +09:00
### **Phase 3: 관리 기능 전환 (2.5주)**
2025-09-29 17:19:31 +09:00
#### 3.1 다국어 서비스 전환 - 25개 호출
2025-09-29 17:19:31 +09:00
- [ ] 재귀 쿼리 (WITH RECURSIVE) 전환
- [ ] 번역 데이터 관리 최적화
- [ ] 다국어 캐시 시스템 구현
2025-09-29 17:19:31 +09:00
#### 3.2 배치 관련 서비스 전환 - 40개 호출 ⭐ 대규모 신규 발견
2025-09-29 17:19:31 +09:00
- [ ] `batchService.ts` (16개) - 배치 작업 관리
- [ ] `batchExternalDbService.ts` (8개) - 배치 외부DB
- [ ] `batchExecutionLogService.ts` (7개) - 배치 실행 로그
- [ ] `batchManagementService.ts` (5개) - 배치 관리
- [ ] `batchSchedulerService.ts` (4개) - 배치 스케줄러
2025-09-29 17:19:31 +09:00
#### 3.3 표준 관리 서비스 전환 - 41개 호출
2025-09-29 17:19:31 +09:00
- [ ] `componentStandardService.ts` (16개) - 컴포넌트 표준 관리
- [ ] `commonCodeService.ts` (15개) - 코드 관리, 계층 구조
- [ ] `layoutService.ts` (10개) - 레이아웃 관리
#### 3.4 데이터플로우 관련 서비스 - 18개 호출 ⭐ 신규 발견
- [ ] `dataflowDiagramService.ts` (12개) - 다이어그램 관리
- [ ] `dataflowControlService.ts` (6개) - 복잡한 제어 로직
#### 3.5 기타 중요 서비스 - 38개 호출 ⭐ 신규 발견
- [ ] `collectionService.ts` (11개) - 컬렉션 관리
- [ ] `dbTypeCategoryService.ts` (10개) - DB 타입 분류
- [ ] `templateStandardService.ts` (9개) - 템플릿 표준
- [ ] `ddlAuditLogger.ts` (8개) - DDL 감사 로그
### **Phase 4: 확장 기능 전환 (2.5주) ⭐ 대폭 확장**
#### 4.1 외부 연동 서비스 - 51개 호출 ⭐ 신규 발견
- [ ] `externalCallConfigService.ts` (8개) - 외부 호출 설정
- [ ] `eventTriggerService.ts` (6개) - JSON 검색 쿼리
- [ ] `enhancedDynamicFormService.ts` (6개) - 확장 동적 폼
- [ ] `ddlExecutionService.ts` (6개) - DDL 실행
- [ ] `entityJoinService.ts` (5개) - 엔티티 조인
- [ ] `dataMappingService.ts` (5개) - 데이터 매핑
- [ ] `authService.ts` (5개) - 사용자 인증
- [ ] `multiConnectionQueryService.ts` (4개) - 외부 DB 연결
- [ ] `dataService.ts` (4개) - 데이터 서비스
- [ ] `adminService.ts` (3개) - 관리자 메뉴
- [ ] `referenceCacheService.ts` (3개) - 캐시 관리
#### 4.2 컨트롤러 레이어 전환 - 72개 호출 ⭐ 대규모 신규 발견
2025-09-29 17:19:31 +09:00
- [ ] `adminController.ts` (28개) - 관리자 컨트롤러
- [ ] `webTypeStandardController.ts` (11개) - 웹타입 표준
- [ ] `fileController.ts` (11개) - 파일 컨트롤러
- [ ] `buttonActionStandardController.ts` (11개) - 버튼 액션 표준
- [ ] `entityReferenceController.ts` (4개) - 엔티티 참조
- [ ] `dataflowExecutionController.ts` (3개) - 데이터플로우 실행
- [ ] `screenFileController.ts` (2개) - 화면 파일
- [ ] `ddlRoutes.ts` (2개) - DDL 라우트
2025-09-29 17:19:31 +09:00
#### 4.3 설정 및 기반 구조 - 6개 호출
2025-09-29 17:19:31 +09:00
- [ ] `database.ts` (4개) - 데이터베이스 설정
- [ ] `companyManagementRoutes.ts` (2개) - 회사 관리 라우트
2025-09-29 17:19:31 +09:00
### **Phase 5: 사용하지 않는 Scripts 삭제 (0.5주) 🗑️**
2025-09-29 17:19:31 +09:00
#### 5.1 불필요한 스크립트 파일 삭제 - 60개 호출 제거
2025-09-29 17:19:31 +09:00
- [ ] `backend-node/scripts/` 전체 폴더 삭제 (53개 호출)
- [ ] `backend-node/clean-screen-tables.js` 삭제 (7개 호출)
- [ ] 관련 package.json 스크립트 정리
- [ ] 문서에서 스크립트 참조 제거
2025-09-29 17:19:31 +09:00
**✅ 효과**: 60개 Prisma 호출을 마이그레이션 없이 제거하여 작업량 대폭 감소
### **Phase 6: Prisma 완전 제거 (0.5주)**
#### 6.1 Prisma 의존성 제거
2025-09-29 17:19:31 +09:00
- [ ] `package.json`에서 Prisma 제거
- [ ] `schema.prisma` 파일 삭제
- [ ] 관련 설정 파일 정리
#### 6.2 최종 검증 및 최적화
2025-09-29 17:19:31 +09:00
- [ ] 전체 기능 테스트
- [ ] 성능 최적화
- [ ] 문서화 업데이트
---
## 🔄 마이그레이션 전략
### 1. **점진적 전환 방식**
#### 단계별 전환
```typescript
// 1단계: 기존 Prisma 코드 유지하면서 Raw Query 병행
class AuthService {
// 기존 방식 (임시 유지)
async loginWithPrisma(userId: string) {
return await prisma.user_info.findUnique({
where: { user_id: userId },
});
}
// 새로운 방식 (점진적 도입)
async loginWithRawQuery(userId: string) {
const { query, params } = QueryBuilder.select("user_info", {
where: { user_id: userId },
});
return await DatabaseManager.query(query, params);
}
}
// 2단계: 기존 메서드를 새로운 방식으로 교체
class AuthService {
async login(userId: string) {
return await this.loginWithRawQuery(userId);
}
}
// 3단계: 기존 코드 완전 제거
```
### 2. **호환성 레이어**
```typescript
// utils/prismaCompatibility.ts
export class PrismaCompatibilityLayer {
// 기존 Prisma 호출을 Raw Query로 변환하는 어댑터
static async findUnique(model: string, options: any) {
const { where } = options;
const { query, params } = QueryBuilder.select(model, { where });
const results = await DatabaseManager.query(query, params);
return results[0] || null;
}
static async findMany(model: string, options: any = {}) {
const { where, orderBy, take: limit, skip: offset } = options;
const { query, params } = QueryBuilder.select(model, {
where,
orderBy,
limit,
offset,
});
return await DatabaseManager.query(query, params);
}
static async create(model: string, options: any) {
const { data } = options;
const { query, params } = QueryBuilder.insert(model, data, {
returning: ["*"],
});
const results = await DatabaseManager.query(query, params);
return results[0];
}
}
```
### 3. **테스트 전략**
#### 병렬 테스트
```typescript
// tests/migration.test.ts
describe("Prisma to Raw Query Migration", () => {
test("AuthService: 동일한 결과 반환", async () => {
const userId = "test_user";
// 기존 Prisma 결과
const prismaResult = await authService.loginWithPrisma(userId);
// 새로운 Raw Query 결과
const rawQueryResult = await authService.loginWithRawQuery(userId);
// 결과 비교
expect(rawQueryResult).toEqual(prismaResult);
});
});
```
---
## 🚨 위험 요소 및 대응 방안
### 1. **데이터 일관성 위험**
#### 위험 요소
- 트랜잭션 처리 미스
- 타입 변환 오류
- NULL 처리 차이
#### 대응 방안
```typescript
// 엄격한 트랜잭션 관리
export class TransactionManager {
static async executeInTransaction<T>(
operations: ((client: PoolClient) => Promise<T>)[]
): Promise<T[]> {
return await DatabaseManager.transaction(async (client) => {
const results: T[] = [];
for (const operation of operations) {
const result = await operation(client);
results.push(result);
}
return results;
});
}
}
// 타입 안전성 검증
export class TypeConverter {
static toPostgresType(value: any, expectedType: string): any {
switch (expectedType) {
case "integer":
return parseInt(value) || null;
case "decimal":
return parseFloat(value) || null;
case "boolean":
return Boolean(value);
case "timestamp":
return value ? new Date(value) : null;
default:
return value;
}
}
}
```
### 2. **성능 저하 위험**
#### 위험 요소
- 연결 풀 관리 미흡
- 쿼리 최적화 부족
- 캐싱 메커니즘 부재
#### 대응 방안
```typescript
// 연결 풀 최적화
export class ConnectionPoolManager {
private static readonly DEFAULT_POOL_CONFIG = {
min: 2,
max: 20,
acquireTimeoutMillis: 30000,
createTimeoutMillis: 30000,
destroyTimeoutMillis: 5000,
idleTimeoutMillis: 30000,
reapIntervalMillis: 1000,
createRetryIntervalMillis: 200,
};
}
// 쿼리 캐싱
export class QueryCache {
private static cache = new Map<string, { data: any; timestamp: number }>();
private static readonly CACHE_TTL = 5 * 60 * 1000; // 5분
static get(key: string): any | null {
const cached = this.cache.get(key);
if (cached && Date.now() - cached.timestamp < this.CACHE_TTL) {
return cached.data;
}
this.cache.delete(key);
return null;
}
static set(key: string, data: any): void {
this.cache.set(key, { data, timestamp: Date.now() });
}
}
```
### 3. **개발 생산성 저하**
#### 위험 요소
- 타입 안전성 부족
- 디버깅 어려움
- 코드 복잡성 증가
#### 대응 방안
```typescript
// 개발자 친화적 인터페이스
export class DatabaseORM {
// Prisma와 유사한 인터페이스 제공
user_info = {
findUnique: (options: { where: Record<string, any> }) =>
PrismaCompatibilityLayer.findUnique("user_info", options),
findMany: (options?: any) =>
PrismaCompatibilityLayer.findMany("user_info", options),
create: (options: { data: Record<string, any> }) =>
PrismaCompatibilityLayer.create("user_info", options),
};
// 다른 테이블들도 동일한 패턴으로 구현
}
// 디버깅 도구
export class QueryLogger {
static log(query: string, params: any[], executionTime: number) {
if (process.env.NODE_ENV === "development") {
console.log(`🔍 Query: ${query}`);
console.log(`📊 Params: ${JSON.stringify(params)}`);
console.log(`⏱️ Time: ${executionTime}ms`);
}
}
}
```
---
## 📈 성능 최적화 전략
### 1. **연결 풀 최적화**
```typescript
// config/optimizedPool.ts
export class OptimizedPoolConfig {
static getConfig() {
return {
// 환경별 최적화된 설정
max: process.env.NODE_ENV === "production" ? 20 : 5,
min: process.env.NODE_ENV === "production" ? 5 : 2,
// 연결 타임아웃 최적화
acquireTimeoutMillis: 30000,
createTimeoutMillis: 30000,
// 유휴 연결 관리
idleTimeoutMillis: 600000, // 10분
// 연결 검증
testOnBorrow: true,
validationQuery: "SELECT 1",
};
}
}
```
### 2. **쿼리 최적화**
```typescript
// utils/queryOptimizer.ts
export class QueryOptimizer {
// 인덱스 힌트 추가
static addIndexHint(query: string, indexName: string): string {
return query.replace(
/FROM\s+(\w+)/i,
`FROM $1 /*+ INDEX($1 ${indexName}) */`
);
}
// 쿼리 분석 및 최적화 제안
static analyzeQuery(query: string): QueryAnalysis {
return {
hasIndex: this.checkIndexUsage(query),
estimatedRows: this.estimateRowCount(query),
suggestions: this.generateOptimizationSuggestions(query),
};
}
}
```
### 3. **캐싱 전략**
```typescript
// utils/smartCache.ts
export class SmartCache {
private static redis: Redis; // Redis 클라이언트
// 테이블별 캐시 전략
static async get(key: string, tableName: string): Promise<any> {
const cacheConfig = this.getCacheConfig(tableName);
if (!cacheConfig.enabled) return null;
const cached = await this.redis.get(key);
return cached ? JSON.parse(cached) : null;
}
static async set(key: string, data: any, tableName: string): Promise<void> {
const cacheConfig = this.getCacheConfig(tableName);
if (cacheConfig.enabled) {
await this.redis.setex(key, cacheConfig.ttl, JSON.stringify(data));
}
}
private static getCacheConfig(tableName: string) {
const configs = {
user_info: { enabled: true, ttl: 300 }, // 5분
menu_info: { enabled: true, ttl: 600 }, // 10분
dynamic_tables: { enabled: false, ttl: 0 }, // 동적 테이블은 캐시 안함
};
return configs[tableName] || { enabled: false, ttl: 0 };
}
}
```
---
## 🧪 테스트 전략
### 1. **단위 테스트**
```typescript
// tests/unit/queryBuilder.test.ts
describe("QueryBuilder", () => {
test("SELECT 쿼리 생성", () => {
const { query, params } = QueryBuilder.select("user_info", {
where: { user_id: "test" },
limit: 10,
});
expect(query).toBe("SELECT * FROM user_info WHERE user_id = $1 LIMIT $2");
expect(params).toEqual(["test", 10]);
});
test("복잡한 JOIN 쿼리", () => {
const { query, params } = QueryBuilder.select("user_info", {
joins: [
{
type: "LEFT",
table: "dept_info",
on: "user_info.dept_code = dept_info.dept_code",
},
],
where: { "user_info.status": "active" },
});
expect(query).toContain("LEFT JOIN dept_info");
expect(query).toContain("WHERE user_info.status = $1");
});
});
```
### 2. **통합 테스트**
```typescript
// tests/integration/migration.test.ts
describe("Migration Integration Tests", () => {
let prismaService: any;
let rawQueryService: any;
beforeAll(async () => {
// 테스트 데이터베이스 설정
await setupTestDatabase();
});
test("동일한 결과 반환 - 사용자 조회", async () => {
const testUserId = "integration_test_user";
const prismaResult = await prismaService.getUser(testUserId);
const rawQueryResult = await rawQueryService.getUser(testUserId);
expect(normalizeResult(rawQueryResult)).toEqual(
normalizeResult(prismaResult)
);
});
test("트랜잭션 일관성 - 복잡한 업데이트", async () => {
const testData = {
/* 테스트 데이터 */
};
// Prisma 트랜잭션
const prismaResult = await prismaService.complexUpdate(testData);
// Raw Query 트랜잭션
const rawQueryResult = await rawQueryService.complexUpdate(testData);
expect(rawQueryResult.success).toBe(prismaResult.success);
});
});
```
### 3. **성능 테스트**
```typescript
// tests/performance/benchmark.test.ts
describe("Performance Benchmarks", () => {
test("대량 데이터 조회 성능", async () => {
const iterations = 1000;
// Prisma 성능 측정
const prismaStart = Date.now();
for (let i = 0; i < iterations; i++) {
await prismaService.getLargeDataset();
}
const prismaTime = Date.now() - prismaStart;
// Raw Query 성능 측정
const rawQueryStart = Date.now();
for (let i = 0; i < iterations; i++) {
await rawQueryService.getLargeDataset();
}
const rawQueryTime = Date.now() - rawQueryStart;
console.log(`Prisma: ${prismaTime}ms, Raw Query: ${rawQueryTime}ms`);
// Raw Query가 더 빠르거나 비슷해야 함
expect(rawQueryTime).toBeLessThanOrEqual(prismaTime * 1.1);
});
});
```
---
## 📋 체크리스트
### **Phase 1: 기반 구조 (1주)** ✅ **완료**
- [x] DatabaseManager 클래스 구현 (`backend-node/src/database/db.ts`)
- [x] QueryBuilder 유틸리티 구현 (`backend-node/src/utils/queryBuilder.ts`)
- [x] 타입 정의 및 검증 로직 (`backend-node/src/types/database.ts`)
- [x] 연결 풀 설정 및 최적화 (pg Pool 사용)
- [x] 트랜잭션 관리 시스템 (transaction 함수 구현)
- [x] 에러 핸들링 메커니즘 (try-catch 및 rollback 처리)
- [x] 로깅 및 모니터링 도구 (쿼리 로그 포함)
- [x] 단위 테스트 작성 (`backend-node/src/tests/`)
- [x] 테스트 성공 확인 (multiConnectionQueryService, externalCallConfigService)
feat: Complete Phase 1.5 - AuthService Raw Query migration Phase 1.5 완료: 인증 서비스 Raw Query 전환 및 테스트 완료 ✅ AuthService 전환 완료 (5개 Prisma 호출 제거): - loginPwdCheck(): Raw Query로 사용자 비밀번호 조회 - insertLoginAccessLog(): Raw Query로 로그인 로그 기록 - getUserInfo(): Raw Query로 사용자/권한/회사 정보 조회 - authority_sub_user ↔ authority_master JOIN (master_objid ↔ objid) - 3개 쿼리로 분리 (사용자, 권한, 회사) - processLogin(): 전체 로그인 플로우 통합 - processLogout(): 로그아웃 로그 기록 🧪 테스트 완료: - 단위 테스트: 30개 테스트 모두 통과 ✅ - 로그인 검증 (6개) - 사용자 정보 조회 (5개) - 로그인 로그 기록 (4개) - 전체 로그인 프로세스 (5개) - 로그아웃 (2개) - 토큰 검증 (3개) - Raw Query 전환 검증 (3개) - 성능 테스트 (2개) - 통합 테스트: 작성 완료 (auth.integration.test.ts) - 로그인 → 토큰 발급 → 인증 → 로그아웃 플로우 🔧 주요 변경사항: - Prisma import 제거 → Raw Query (query from db.ts) - authority 테이블 JOIN 수정 (auth_code → master_objid/objid) - 파라미터 바인딩으로 SQL Injection 방지 - 타입 안전성 유지 (TypeScript Generic 사용) 📊 성능: - 로그인 프로세스: < 1초 - 사용자 정보 조회: < 500ms - 모든 테스트 실행 시간: 2.016초 🎯 다음 단계: - Phase 2: 핵심 서비스 전환 (ScreenManagement, TableManagement 등) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
2025-09-30 15:59:32 +09:00
### **Phase 1.5: 인증 및 관리자 서비스 (우선 전환) - 36개 호출** ✅ **완료**
> **우선순위 변경**: Phase 2 진행 전 인증/관리 시스템을 먼저 전환하여 전체 시스템의 안정적인 기반 구축
feat: Complete Phase 1.5 - AuthService Raw Query migration Phase 1.5 완료: 인증 서비스 Raw Query 전환 및 테스트 완료 ✅ AuthService 전환 완료 (5개 Prisma 호출 제거): - loginPwdCheck(): Raw Query로 사용자 비밀번호 조회 - insertLoginAccessLog(): Raw Query로 로그인 로그 기록 - getUserInfo(): Raw Query로 사용자/권한/회사 정보 조회 - authority_sub_user ↔ authority_master JOIN (master_objid ↔ objid) - 3개 쿼리로 분리 (사용자, 권한, 회사) - processLogin(): 전체 로그인 플로우 통합 - processLogout(): 로그아웃 로그 기록 🧪 테스트 완료: - 단위 테스트: 30개 테스트 모두 통과 ✅ - 로그인 검증 (6개) - 사용자 정보 조회 (5개) - 로그인 로그 기록 (4개) - 전체 로그인 프로세스 (5개) - 로그아웃 (2개) - 토큰 검증 (3개) - Raw Query 전환 검증 (3개) - 성능 테스트 (2개) - 통합 테스트: 작성 완료 (auth.integration.test.ts) - 로그인 → 토큰 발급 → 인증 → 로그아웃 플로우 🔧 주요 변경사항: - Prisma import 제거 → Raw Query (query from db.ts) - authority 테이블 JOIN 수정 (auth_code → master_objid/objid) - 파라미터 바인딩으로 SQL Injection 방지 - 타입 안전성 유지 (TypeScript Generic 사용) 📊 성능: - 로그인 프로세스: < 1초 - 사용자 정보 조회: < 500ms - 모든 테스트 실행 시간: 2.016초 🎯 다음 단계: - Phase 2: 핵심 서비스 전환 (ScreenManagement, TableManagement 등) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
2025-09-30 15:59:32 +09:00
- [x] **AuthService 전환 (5개)** - 🔐 최우선 ✅ **완료**
- [x] 로그인 로직 (JWT 생성) - `loginPwdCheck()` Raw Query 전환
- [x] 사용자 인증 및 검증 - `getUserInfo()` Raw Query 전환
- [x] 비밀번호 암호화 처리 - EncryptUtil 유지
- [x] 토큰 관리 - `getUserInfoFromToken()` 정상 동작
- [x] 로그인 로그 기록 - `insertLoginAccessLog()` Raw Query 전환
- [ ] **AdminService 확인 (3개)** - 👤 사용자 관리 (이미 Raw Query 사용)
- [x] 사용자 CRUD - Raw Query 사용 확인
- [x] 메뉴 관리 (재귀 쿼리) - WITH RECURSIVE 사용 확인
- [x] 권한 관리 - Raw Query 사용 확인
- [ ] **AdminController 전환 (28개)** - 📡 관리자 API (Phase 2에서 처리)
- [ ] 사용자 관리 API
- [ ] 메뉴 관리 API
- [ ] 권한 관리 API
- [ ] 회사 관리 API
feat: Complete Phase 1.5 - AuthService Raw Query migration Phase 1.5 완료: 인증 서비스 Raw Query 전환 및 테스트 완료 ✅ AuthService 전환 완료 (5개 Prisma 호출 제거): - loginPwdCheck(): Raw Query로 사용자 비밀번호 조회 - insertLoginAccessLog(): Raw Query로 로그인 로그 기록 - getUserInfo(): Raw Query로 사용자/권한/회사 정보 조회 - authority_sub_user ↔ authority_master JOIN (master_objid ↔ objid) - 3개 쿼리로 분리 (사용자, 권한, 회사) - processLogin(): 전체 로그인 플로우 통합 - processLogout(): 로그아웃 로그 기록 🧪 테스트 완료: - 단위 테스트: 30개 테스트 모두 통과 ✅ - 로그인 검증 (6개) - 사용자 정보 조회 (5개) - 로그인 로그 기록 (4개) - 전체 로그인 프로세스 (5개) - 로그아웃 (2개) - 토큰 검증 (3개) - Raw Query 전환 검증 (3개) - 성능 테스트 (2개) - 통합 테스트: 작성 완료 (auth.integration.test.ts) - 로그인 → 토큰 발급 → 인증 → 로그아웃 플로우 🔧 주요 변경사항: - Prisma import 제거 → Raw Query (query from db.ts) - authority 테이블 JOIN 수정 (auth_code → master_objid/objid) - 파라미터 바인딩으로 SQL Injection 방지 - 타입 안전성 유지 (TypeScript Generic 사용) 📊 성능: - 로그인 프로세스: < 1초 - 사용자 정보 조회: < 500ms - 모든 테스트 실행 시간: 2.016초 🎯 다음 단계: - Phase 2: 핵심 서비스 전환 (ScreenManagement, TableManagement 등) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
2025-09-30 15:59:32 +09:00
- [x] **테스트****완료**
- [x] 단위 테스트 (30개 테스트 모두 통과)
- [x] 통합 테스트 작성 완료
### **Phase 2: 핵심 서비스 (3주) - 107개 호출**
2025-09-30 17:40:21 +09:00
#### ✅ 완료된 서비스
- [x] **ScreenManagementService 전환 (46개)****완료** (Phase 2.1)
2025-09-30 17:40:21 +09:00
- [x] 46개 Prisma 호출 전환 완료
- [x] 18개 단위 테스트 통과
- [x] 6개 통합 테스트 작성 완료
- [x] 실제 운영 버그 발견 및 수정 (소수점 좌표)
- 📄 **[PHASE2_SCREEN_MANAGEMENT_MIGRATION.md](PHASE2_SCREEN_MANAGEMENT_MIGRATION.md)**
- [x] **TableManagementService 전환 (33개)****완료** (Phase 2.2)
2025-09-30 17:40:21 +09:00
- [x] 33개 Prisma 호출 전환 완료 ($queryRaw 26개 + ORM 7개)
- [x] 단위 테스트 작성 완료
- [x] Prisma import 완전 제거
2025-09-30 17:40:21 +09:00
- 📄 **[PHASE2.2_TABLE_MANAGEMENT_MIGRATION.md](PHASE2.2_TABLE_MANAGEMENT_MIGRATION.md)**
- [x] **DDLExecutionService 전환 (6개)****완료** (Phase 2.3)
2025-10-01 10:03:41 +09:00
- [x] 6개 Prisma 호출 전환 완료 (트랜잭션 2개 + $queryRawUnsafe 2개 + ORM 2개)
- [x] **테이블 동적 생성/수정/삭제 기능 완료**
- [x] ✅ 단위 테스트 8개 모두 통과
- [x] Prisma import 완전 제거
- 📄 **[PHASE2.7_DDL_EXECUTION_MIGRATION.md](PHASE2.7_DDL_EXECUTION_MIGRATION.md)**
2025-10-01 10:03:41 +09:00
- [x] **DataflowService 전환 (31개)****완료** (Phase 2.3)
- [x] 31개 Prisma 호출 전환 완료 (복잡한 관계 관리 + 트랜잭션)
- [x] 테이블 관계 관리 (8개) + 브리지 관리 (6개) + 통계/조회 (4개) + 복잡한 기능 (3개)
- [x] TypeScript 컴파일 성공
- [x] Prisma import 완전 제거
- 📄 **[PHASE2.3_DATAFLOW_SERVICE_MIGRATION.md](PHASE2.3_DATAFLOW_SERVICE_MIGRATION.md)**
#### ⏳ 진행 예정 서비스
- [x] **DynamicFormService 전환 (13개)****완료** (Phase 2.4)
- [x] 13개 Prisma 호출 전환 완료 (동적 폼 CRUD + UPSERT)
- [x] 동적 UPSERT 쿼리 구현 (ON CONFLICT 구문)
- [x] 부분 업데이트 및 타입 변환 로직 유지
- [x] TypeScript 컴파일 성공
- [x] Prisma import 완전 제거
2025-09-30 17:40:21 +09:00
- 📄 **[PHASE2.4_DYNAMIC_FORM_MIGRATION.md](PHASE2.4_DYNAMIC_FORM_MIGRATION.md)**
- [x] **ExternalDbConnectionService 전환 (15개)****완료** (Phase 2.5)
- [x] 15개 Prisma 호출 전환 완료 (외부 DB 연결 CRUD + 테스트)
- [x] 동적 WHERE 조건 생성 및 동적 UPDATE 쿼리 구현
- [x] 암호화/복호화 로직 유지
- [x] TypeScript 컴파일 성공
- [x] Prisma import 완전 제거
2025-09-30 17:40:21 +09:00
- 📄 **[PHASE2.5_EXTERNAL_DB_CONNECTION_MIGRATION.md](PHASE2.5_EXTERNAL_DB_CONNECTION_MIGRATION.md)**
- [x] **DataflowControlService 전환 (6개)****완료** (Phase 2.6)
- [x] 6개 Prisma 호출 전환 완료 (데이터플로우 제어 + 동적 테이블 CRUD)
- [x] 파라미터 바인딩 수정 (MySQL → PostgreSQL 스타일)
- [x] 복잡한 비즈니스 로직 유지
- [x] TypeScript 컴파일 성공
- [x] Prisma import 완전 제거
2025-09-30 17:40:21 +09:00
- 📄 **[PHASE2.6_DATAFLOW_CONTROL_MIGRATION.md](PHASE2.6_DATAFLOW_CONTROL_MIGRATION.md)**
#### ✅ 다른 Phase로 이동
- [x] ~~AuthService 전환 (5개)~~ → Phase 1.5로 이동
- [x] ~~MultiConnectionQueryService 전환 (4개)~~ → Phase 1 완료
2025-09-29 17:19:31 +09:00
### **Phase 3: 관리 기능 (2.5주) - 162개 호출**
- [x] **MultiLangService 전환 (25개)****완료** (Phase 3.1)
- [x] 25개 Prisma 호출 전환 완료 (다국어 관리 CRUD)
- [x] 동적 WHERE 조건 및 동적 UPDATE 쿼리 구현
- [x] 트랜잭션 처리 (삭제 + 삽입)
- [x] JOIN 쿼리 (multi_lang_text + multi_lang_key_master)
- [x] IN 절 동적 파라미터 바인딩
- [x] TypeScript 컴파일 성공
- [x] Prisma import 완전 제거
- [x] **BatchService 전환 (14개)****완료** (Phase 3.2)
- [x] 14개 Prisma 호출 전환 완료 (배치 설정 CRUD)
- [x] 동적 WHERE 조건 생성 (ILIKE 검색, 페이지네이션)
- [x] 동적 UPDATE 쿼리 (변경된 필드만 업데이트)
- [x] 복잡한 트랜잭션 (배치 설정 + 매핑 동시 생성/수정/삭제)
- [x] LEFT JOIN으로 배치 매핑 조회 (json_agg, COALESCE)
- [x] transaction 함수 활용 (client.query().rows 처리)
- [x] TypeScript 컴파일 성공
- [x] Prisma import 완전 제거
- [ ] 배치 관련 서비스 전환 (26개) ⭐ 대규모 신규 발견
- [ ] BatchExternalDbService (8개)
- [ ] BatchExecutionLogService (7개), BatchManagementService (5개)
- [ ] BatchSchedulerService (4개)
- [ ] 표준 관리 서비스 전환 (41개)
- [ ] ComponentStandardService (16개), CommonCodeService (15개)
- [ ] LayoutService (10개)
- [ ] 데이터플로우 관련 서비스 (18개) ⭐ 신규 발견
- [ ] DataflowDiagramService (12개), DataflowControlService (6개)
- [ ] 기타 중요 서비스 (38개) ⭐ 신규 발견
- [ ] CollectionService (11개), DbTypeCategoryService (10개)
- [ ] TemplateStandardService (9개), DDLAuditLogger (8개)
2025-09-29 17:19:31 +09:00
- [ ] 기능별 테스트 완료
### **Phase 4: 확장 기능 (2.5주) - 129개 호출 ⭐ 대폭 확장**
- [ ] 외부 연동 서비스 전환 (51개) ⭐ 신규 발견
- [ ] ExternalCallConfigService (8개), EventTriggerService (6개)
- [ ] EnhancedDynamicFormService (6개), EntityJoinService (5개)
- [ ] DataMappingService (5개), DataService (4개)
- [ ] AdminService (3개), ReferenceCacheService (3개)
- [ ] 컨트롤러 레이어 전환 (72개) ⭐ 대규모 신규 발견
- [ ] AdminController (28개), WebTypeStandardController (11개)
- [ ] FileController (11개), ButtonActionStandardController (11개)
- [ ] EntityReferenceController (4개), DataflowExecutionController (3개)
- [ ] ScreenFileController (2개), DDLRoutes (2개)
- [ ] 설정 및 기반 구조 (6개)
- [ ] Database.ts (4개), CompanyManagementRoutes (2개)
2025-09-29 17:19:31 +09:00
- [ ] 전체 기능 테스트
### **Phase 5: Scripts 삭제 (0.5주) - 60개 호출 제거 🗑️**
- [ ] 불필요한 스크립트 파일 삭제 (60개) 🗑️ 마이그레이션 불필요
- [ ] backend-node/scripts/ 전체 폴더 삭제 (53개)
- [ ] backend-node/clean-screen-tables.js 삭제 (7개)
- [ ] package.json 스크립트 정리
- [ ] 문서에서 스크립트 참조 제거
### **Phase 6: 완전 제거 (0.5주)**
2025-09-29 17:19:31 +09:00
- [ ] Prisma 의존성 제거
- [ ] schema.prisma 삭제
- [ ] 관련 설정 파일 정리
- [ ] 문서 업데이트
- [ ] 최종 성능 테스트
- [ ] 배포 준비
---
## 🎯 성공 기준
### **기능적 요구사항**
- [ ] 모든 기존 기능이 동일하게 작동
- [ ] 동적 테이블 생성/관리 완벽 지원
- [ ] 트랜잭션 일관성 보장
- [ ] 에러 처리 및 복구 메커니즘
### **성능 요구사항**
- [ ] 기존 대비 성능 저하 없음 (±10% 이내)
- [ ] 메모리 사용량 최적화
- [ ] 연결 풀 효율성 개선
- [ ] 쿼리 실행 시간 단축
### **품질 요구사항**
- [ ] 코드 커버리지 90% 이상
- [ ] 모든 테스트 케이스 통과
- [ ] 타입 안전성 보장
- [ ] 보안 검증 완료
---
## 📚 참고 자료
### **기술 문서**
- [PostgreSQL 공식 문서](https://www.postgresql.org/docs/)
- [Node.js pg 라이브러리](https://node-postgres.com/)
- [SQL 쿼리 최적화 가이드](https://use-the-index-luke.com/)
### **내부 문서**
- [현재 데이터베이스 스키마](backend-node/prisma/schema.prisma)
- [기존 Java 시스템 구조](src/com/pms/)
- [동적 테이블 생성 계획서](테이블_동적_생성_기능_개발_계획서.md)
---
## ⚠️ 주의사항
1. **데이터 백업**: 마이그레이션 전 전체 데이터베이스 백업 필수
2. **점진적 전환**: 한 번에 모든 것을 바꾸지 말고 단계별로 진행
3. **철저한 테스트**: 각 단계마다 충분한 테스트 수행
4. **롤백 계획**: 문제 발생 시 즉시 롤백할 수 있는 계획 수립
5. **모니터링**: 전환 후 성능 및 안정성 지속 모니터링
---
---
## 📈 **업데이트된 마이그레이션 규모**
### **🔍 최종 Prisma 사용 현황 (Scripts 삭제 후)**
- **기존 계획**: 42개 파일, 386개 호출
- **Scripts 포함**: 52개 파일, 490개 호출 (+104개 호출 발견)
- **Scripts 삭제 후**: **42개 파일, 444개 호출** (+58개 호출 실제 증가) ⚡
### **⭐ 주요 신규 발견 서비스들**
1. **`dataflowService.ts`** (31개) - 데이터플로우 관리 핵심 서비스
2. **배치 관련 서비스들** (40개) - 5개 서비스로 분산된 대규모 배치 시스템
3. **`dataflowDiagramService.ts`** (12개) - 다이어그램 관리
4. **`dbTypeCategoryService.ts`** (10개) - DB 타입 분류 시스템
5. **컨트롤러 레이어** (72개) - 7개 컨트롤러에서 대규모 Prisma 사용
6. **감사 및 로깅 서비스들** (15개) - DDL 감사, 배치 실행 로그
7. **확장 기능들** (26개) - 엔티티 조인, 데이터 매핑, 외부 호출 설정
8. **🗑️ Scripts 삭제** (60개) - 사용하지 않는 개발/배포 스크립트 (마이그레이션 불필요)
### **📊 우선순위 재조정**
#### **🔴 최우선 (Phase 2) - 107개 호출**
- 화면관리 (46개), 테이블관리 (35개), 데이터플로우 (31개)
#### **🟡 고우선순위 (Phase 3) - 162개 호출**
- 다국어 (25개), 배치 시스템 (40개), 표준 관리 (41개)
#### **🟢 중간우선순위 (Phase 4) - 129개 호출**
- 외부 연동 (51개), 컨트롤러 레이어 (72개), 기타 (6개)
#### **🗑️ Scripts 삭제 (Phase 5) - 60개 호출** 🗑️ 마이그레이션 불필요
- 사용하지 않는 개발/배포 스크립트 (60개) - 삭제로 작업량 감소
---
## 🎯 **최종 마이그레이션 계획**
**총 예상 기간: 8주** ⬆️ (+2주 연장, Scripts 삭제로 1주 단축)
**핵심 개발자: 3-4명** ⬆️ (+1명 추가)
**실제 마이그레이션 대상: 444개 호출** (Scripts 60개 제외)
**위험도: 중간-높음** ⬇️ (Scripts 삭제로 위험도 일부 감소)
### **⚠️ 주요 위험 요소**
1. **배치 시스템 복잡성**: 5개 서비스 40개 호출의 복잡한 의존성
2. **컨트롤러 레이어 규모**: 72개 호출의 대규모 API 전환
3. **데이터플로우 시스템**: 신규 발견된 핵심 서비스 (31개 호출)
4. **트랜잭션 복잡성**: 다중 서비스 간 데이터 일관성 보장
5. **✅ Scripts 삭제**: 60개 호출 제거로 작업량 대폭 감소
### **🚀 성공을 위한 핵심 전략**
1. **단계별 점진적 전환**: 절대 한 번에 모든 것을 바꾸지 않기
2. **철저한 테스트**: 각 Phase마다 완전한 기능 테스트
3. **롤백 계획**: 각 단계별 즉시 롤백 가능한 계획 수립
4. **모니터링 강화**: 전환 후 성능 및 안정성 지속 모니터링
5. **팀 확대**: 복잡성 증가로 인한 개발팀 확대 필요
이 **완전한 분석**을 통해 Prisma를 완전히 제거하고 진정한 동적 데이터베이스 시스템을 구축할 수 있습니다! 🚀
2025-09-29 17:19:31 +09:00
**⚡ 중요**: 이제 모든 Prisma 사용 부분이 파악되었으므로, 누락 없는 완전한 마이그레이션이 가능합니다.