phase 2 변환계획 작성

This commit is contained in:
kjs 2025-09-30 17:40:21 +09:00
parent 1a640850c5
commit f9f31c7bd3
7 changed files with 1869 additions and 10 deletions

View File

@ -0,0 +1,428 @@
# 🗂️ Phase 2.2: TableManagementService Raw Query 전환 계획
## 📋 개요
TableManagementService는 **33개의 Prisma 호출**이 있습니다. 대부분(약 26개)은 `$queryRaw`를 사용하고 있어 SQL은 이미 작성되어 있지만, **Prisma 클라이언트를 완전히 제거하려면 33개 모두를 `db.ts``query` 함수로 교체**해야 합니다.
### 📊 기본 정보
| 항목 | 내용 |
| --------------- | ----------------------------------------------------- |
| 파일 위치 | `backend-node/src/services/tableManagementService.ts` |
| 파일 크기 | 3,178 라인 |
| Prisma 호출 | 33개 ($queryRaw: 26개, ORM: 7개) |
| **현재 진행률** | **0/33 (0%)****전환 필요** |
| **전환 필요** | **33개 모두 전환 필요** (SQL은 이미 작성되어 있음) |
| 복잡도 | 중간 (SQL 작성은 완료, `query()` 함수로 교체만 필요) |
| 우선순위 | 🟡 중간 (Phase 2.2) |
### 🎯 전환 목표
- ✅ **33개 모든 Prisma 호출을 `db.ts``query()` 함수로 교체**
- 26개 `$queryRaw``query()` 또는 `queryOne()`
- 7개 ORM 메서드 → `query()` (SQL 새로 작성)
- 1개 `$transaction``transaction()`
- ✅ 트랜잭션 처리 정상 동작 확인
- ✅ 모든 단위 테스트 통과
- ✅ **Prisma import 완전 제거**
---
## 🔍 Prisma 사용 현황 분석
### 1. `$queryRaw` / `$queryRawUnsafe` 사용 (26개)
**현재 상태**: SQL은 이미 작성되어 있음 ✅
**전환 작업**: `prisma.$queryRaw``query()` 함수로 교체만 하면 됨
```typescript
// 기존
await prisma.$queryRaw`SELECT ...`;
await prisma.$queryRawUnsafe(sqlString, ...params);
// 전환 후
import { query } from "../database/db";
await query(`SELECT ...`);
await query(sqlString, params);
```
### 2. ORM 메서드 사용 (7개)
**현재 상태**: Prisma ORM 메서드 사용
**전환 작업**: SQL 작성 필요
#### 1. table_labels 관리 (2개)
```typescript
// Line 254: 테이블 라벨 UPSERT
await prisma.table_labels.upsert({
where: { table_name: tableName },
update: {},
create: { table_name, table_label, description }
});
// Line 437: 테이블 라벨 조회
await prisma.table_labels.findUnique({
where: { table_name: tableName },
select: { table_name, table_label, description, ... }
});
```
#### 2. column_labels 관리 (5개)
```typescript
// Line 323: 컬럼 라벨 UPSERT
await prisma.column_labels.upsert({
where: {
table_name_column_name: {
table_name: tableName,
column_name: columnName
}
},
update: { column_label, input_type, ... },
create: { table_name, column_name, ... }
});
// Line 481: 컬럼 라벨 조회
await prisma.column_labels.findUnique({
where: {
table_name_column_name: {
table_name: tableName,
column_name: columnName
}
},
select: { id, table_name, column_name, ... }
});
// Line 567: 컬럼 존재 확인
await prisma.column_labels.findFirst({
where: { table_name, column_name }
});
// Line 586: 컬럼 라벨 업데이트
await prisma.column_labels.update({
where: { id: existingColumn.id },
data: { web_type, detail_settings, ... }
});
// Line 610: 컬럼 라벨 생성
await prisma.column_labels.create({
data: { table_name, column_name, web_type, ... }
});
// Line 1003: 파일 타입 컬럼 조회
await prisma.column_labels.findMany({
where: { table_name, web_type: 'file' },
select: { column_name }
});
// Line 1382: 컬럼 웹타입 정보 조회
await prisma.column_labels.findFirst({
where: { table_name, column_name },
select: { web_type, code_category, ... }
});
// Line 2690: 컬럼 라벨 UPSERT (복제)
await prisma.column_labels.upsert({
where: {
table_name_column_name: { table_name, column_name }
},
update: { column_label, web_type, ... },
create: { table_name, column_name, ... }
});
```
#### 3. attach_file_info 관리 (2개)
```typescript
// Line 914: 파일 정보 조회
await prisma.attach_file_info.findMany({
where: { target_objid, doc_type, status: 'ACTIVE' },
select: { objid, real_file_name, file_size, ... },
orderBy: { regdate: 'desc' }
});
// Line 959: 파일 경로로 파일 정보 조회
await prisma.attach_file_info.findFirst({
where: { file_path, status: 'ACTIVE' },
select: { objid, real_file_name, ... }
});
```
#### 4. 트랜잭션 (1개)
```typescript
// Line 391: 전체 컬럼 설정 일괄 업데이트
await prisma.$transaction(async (tx) => {
await this.insertTableIfNotExists(tableName);
for (const columnSetting of columnSettings) {
await this.updateColumnSettings(tableName, columnName, columnSetting);
}
});
```
---
## 📝 전환 예시
### 예시 1: table_labels UPSERT 전환
**기존 Prisma 코드:**
```typescript
await prisma.table_labels.upsert({
where: { table_name: tableName },
update: {},
create: {
table_name: tableName,
table_label: tableName,
description: "",
},
});
```
**새로운 Raw Query 코드:**
```typescript
import { query } from "../database/db";
await query(
`INSERT INTO table_labels (table_name, table_label, description, created_date, updated_date)
VALUES ($1, $2, $3, NOW(), NOW())
ON CONFLICT (table_name) DO NOTHING`,
[tableName, tableName, ""]
);
```
### 예시 2: column_labels UPSERT 전환
**기존 Prisma 코드:**
```typescript
await prisma.column_labels.upsert({
where: {
table_name_column_name: {
table_name: tableName,
column_name: columnName,
},
},
update: {
column_label: settings.columnLabel,
input_type: settings.inputType,
detail_settings: settings.detailSettings,
updated_date: new Date(),
},
create: {
table_name: tableName,
column_name: columnName,
column_label: settings.columnLabel,
input_type: settings.inputType,
detail_settings: settings.detailSettings,
},
});
```
**새로운 Raw Query 코드:**
```typescript
await query(
`INSERT INTO column_labels (
table_name, column_name, column_label, input_type, detail_settings,
code_category, code_value, reference_table, reference_column,
display_column, display_order, is_visible, created_date, updated_date
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, NOW(), NOW())
ON CONFLICT (table_name, column_name)
DO UPDATE SET
column_label = EXCLUDED.column_label,
input_type = EXCLUDED.input_type,
detail_settings = EXCLUDED.detail_settings,
code_category = EXCLUDED.code_category,
code_value = EXCLUDED.code_value,
reference_table = EXCLUDED.reference_table,
reference_column = EXCLUDED.reference_column,
display_column = EXCLUDED.display_column,
display_order = EXCLUDED.display_order,
is_visible = EXCLUDED.is_visible,
updated_date = NOW()`,
[
tableName,
columnName,
settings.columnLabel,
settings.inputType,
settings.detailSettings,
settings.codeCategory,
settings.codeValue,
settings.referenceTable,
settings.referenceColumn,
settings.displayColumn,
settings.displayOrder || 0,
settings.isVisible !== undefined ? settings.isVisible : true,
]
);
```
### 예시 3: 트랜잭션 전환
**기존 Prisma 코드:**
```typescript
await prisma.$transaction(async (tx) => {
await this.insertTableIfNotExists(tableName);
for (const columnSetting of columnSettings) {
await this.updateColumnSettings(tableName, columnName, columnSetting);
}
});
```
**새로운 Raw Query 코드:**
```typescript
import { transaction } from "../database/db";
await transaction(async (client) => {
// 테이블 라벨 자동 추가
await client.query(
`INSERT INTO table_labels (table_name, table_label, description, created_date, updated_date)
VALUES ($1, $2, $3, NOW(), NOW())
ON CONFLICT (table_name) DO NOTHING`,
[tableName, tableName, ""]
);
// 각 컬럼 설정 업데이트
for (const columnSetting of columnSettings) {
const columnName = columnSetting.columnName;
if (columnName) {
await client.query(
`INSERT INTO column_labels (...)
VALUES (...)
ON CONFLICT (table_name, column_name) DO UPDATE SET ...`,
[...]
);
}
}
});
```
---
## 🧪 테스트 계획
### 단위 테스트 (10개)
```typescript
describe("TableManagementService Raw Query 전환 테스트", () => {
describe("insertTableIfNotExists", () => {
test("테이블 라벨 UPSERT 성공", async () => { ... });
test("중복 테이블 처리", async () => { ... });
});
describe("updateColumnSettings", () => {
test("컬럼 설정 UPSERT 성공", async () => { ... });
test("기존 컬럼 업데이트", async () => { ... });
});
describe("getTableLabels", () => {
test("테이블 라벨 조회 성공", async () => { ... });
});
describe("getColumnLabels", () => {
test("컬럼 라벨 조회 성공", async () => { ... });
});
describe("updateAllColumnSettings", () => {
test("일괄 업데이트 성공 (트랜잭션)", async () => { ... });
test("부분 실패 시 롤백", async () => { ... });
});
describe("getFileInfoByColumnAndTarget", () => {
test("파일 정보 조회 성공", async () => { ... });
});
});
```
### 통합 테스트 (5개 시나리오)
```typescript
describe("테이블 관리 통합 테스트", () => {
test("테이블 라벨 생성 → 조회 → 수정", async () => { ... });
test("컬럼 라벨 생성 → 조회 → 수정", async () => { ... });
test("컬럼 일괄 설정 업데이트", async () => { ... });
test("파일 정보 조회 및 보강", async () => { ... });
test("트랜잭션 롤백 테스트", async () => { ... });
});
```
---
## 📋 체크리스트
### 1단계: table_labels 전환 (2개 함수) ⏳ **진행 예정**
- [ ] `insertTableIfNotExists()` - UPSERT
- [ ] `getTableLabels()` - 조회
### 2단계: column_labels 전환 (5개 함수) ⏳ **진행 예정**
- [ ] `updateColumnSettings()` - UPSERT
- [ ] `getColumnLabels()` - 조회
- [ ] `updateColumnWebType()` - findFirst + update/create
- [ ] `getColumnWebTypeInfo()` - findFirst
- [ ] `updateColumnLabel()` - UPSERT (복제)
### 3단계: attach_file_info 전환 (2개 함수) ⏳ **진행 예정**
- [ ] `getFileInfoByColumnAndTarget()` - findMany
- [ ] `getFileInfoByPath()` - findFirst
### 4단계: 트랜잭션 전환 (1개 함수) ⏳ **진행 예정**
- [ ] `updateAllColumnSettings()` - 트랜잭션
### 5단계: 테스트 & 검증 ⏳ **진행 예정**
- [ ] 단위 테스트 작성 (10개)
- [ ] 통합 테스트 작성 (5개 시나리오)
- [ ] Prisma import 완전 제거 확인
- [ ] 성능 테스트
---
## 🎯 완료 기준
- [ ] **33개 모든 Prisma 호출을 Raw Query로 전환 완료**
- [ ] 26개 `$queryRaw``query()` 함수로 교체
- [ ] 7개 ORM 메서드 → `query()` 함수로 전환 (SQL 작성)
- [ ] **모든 TypeScript 컴파일 오류 해결**
- [ ] **트랜잭션 정상 동작 확인**
- [ ] **에러 처리 및 롤백 정상 동작**
- [ ] **모든 단위 테스트 통과 (10개)**
- [ ] **모든 통합 테스트 작성 완료 (5개 시나리오)**
- [ ] **`import prisma` 완전 제거 및 `import { query, transaction } from "../database/db"` 사용**
- [ ] **성능 저하 없음 (기존 대비 ±10% 이내)**
---
## 💡 특이사항
### SQL은 이미 대부분 작성되어 있음
이 서비스는 이미 79%가 `$queryRaw`를 사용하고 있어, **SQL 작성은 완료**되었습니다:
- ✅ `information_schema` 조회: SQL 작성 완료 (`$queryRaw` 사용 중)
- ✅ 동적 테이블 쿼리: SQL 작성 완료 (`$queryRawUnsafe` 사용 중)
- ✅ DDL 실행: SQL 작성 완료 (`$executeRaw` 사용 중)
- ⏳ **전환 작업**: `prisma.$queryRaw``query()` 함수로 **단순 교체만 필요**
- ⏳ CRUD 작업: 7개만 SQL 새로 작성 필요
### UPSERT 패턴 중요
대부분의 전환이 UPSERT 패턴이므로 PostgreSQL의 `ON CONFLICT` 구문을 활용합니다.
---
**작성일**: 2025-09-30
**예상 소요 시간**: 1-1.5일 (SQL은 79% 작성 완료, 함수 교체 작업 필요)
**담당자**: 백엔드 개발팀
**우선순위**: 🟡 중간 (Phase 2.2)
**상태**: ⏳ **진행 예정**
**특이사항**: SQL은 대부분 작성되어 있어 `prisma.$queryRaw``query()` 단순 교체 작업이 주요 작업

View File

@ -0,0 +1,708 @@
# 📊 Phase 2.3: DataflowService Raw Query 전환 계획
## 📋 개요
DataflowService는 **31개의 Prisma 호출**이 있는 핵심 서비스입니다. 테이블 간 관계 관리, 데이터플로우 다이어그램, 데이터 연결 브리지 등 복잡한 기능을 포함합니다.
### 📊 기본 정보
| 항목 | 내용 |
| --------------- | ---------------------------------------------- |
| 파일 위치 | `backend-node/src/services/dataflowService.ts` |
| 파일 크기 | 1,100+ 라인 |
| Prisma 호출 | 31개 |
| **현재 진행률** | **0/31 (0%)****진행 예정** |
| 복잡도 | 매우 높음 (트랜잭션 + 복잡한 관계 관리) |
| 우선순위 | 🔴 최우선 (Phase 2.3) |
### 🎯 전환 목표
- ✅ 31개 Prisma 호출을 모두 Raw Query로 전환
- ✅ 트랜잭션 처리 정상 동작 확인
- ✅ 에러 처리 및 롤백 정상 동작
- ✅ 모든 단위 테스트 통과 (20개 이상)
- ✅ 통합 테스트 작성 완료
- ✅ Prisma import 완전 제거
---
## 🔍 Prisma 사용 현황 분석
### 1. 테이블 관계 관리 (Table Relationships) - 22개
#### 1.1 관계 생성 (3개)
```typescript
// Line 48: 최대 diagram_id 조회
await prisma.table_relationships.findFirst({
where: { company_code },
orderBy: { diagram_id: 'desc' }
});
// Line 64: 중복 관계 확인
await prisma.table_relationships.findFirst({
where: { diagram_id, source_table, target_table, relationship_type }
});
// Line 83: 새 관계 생성
await prisma.table_relationships.create({
data: { diagram_id, source_table, target_table, ... }
});
```
#### 1.2 관계 조회 (6개)
```typescript
// Line 128: 관계 목록 조회
await prisma.table_relationships.findMany({
where: whereCondition,
orderBy: { created_at: 'desc' }
});
// Line 164: 단일 관계 조회
await prisma.table_relationships.findFirst({
where: whereCondition
});
// Line 287: 회사별 관계 조회
await prisma.table_relationships.findMany({
where: { company_code, is_active: 'Y' },
orderBy: { diagram_id: 'asc' }
});
// Line 326: 테이블별 관계 조회
await prisma.table_relationships.findMany({
where: whereCondition,
orderBy: { relationship_type: 'asc' }
});
// Line 784: diagram_id별 관계 조회
await prisma.table_relationships.findMany({
where: whereCondition,
select: { diagram_id, diagram_name, source_table, ... }
});
// Line 883: 회사 코드로 전체 조회
await prisma.table_relationships.findMany({
where: { company_code, is_active: 'Y' }
});
```
#### 1.3 통계 조회 (3개)
```typescript
// Line 362: 전체 관계 수
await prisma.table_relationships.count({
where: whereCondition,
});
// Line 367: 관계 타입별 통계
await prisma.table_relationships.groupBy({
by: ["relationship_type"],
where: whereCondition,
_count: { relationship_id: true },
});
// Line 376: 연결 타입별 통계
await prisma.table_relationships.groupBy({
by: ["connection_type"],
where: whereCondition,
_count: { relationship_id: true },
});
```
#### 1.4 관계 수정/삭제 (5개)
```typescript
// Line 209: 관계 수정
await prisma.table_relationships.update({
where: { relationship_id },
data: { source_table, target_table, ... }
});
// Line 248: 소프트 삭제
await prisma.table_relationships.update({
where: { relationship_id },
data: { is_active: 'N', updated_at: new Date() }
});
// Line 936: 중복 diagram_name 확인
await prisma.table_relationships.findFirst({
where: { company_code, diagram_name, is_active: 'Y' }
});
// Line 953: 최대 diagram_id 조회 (복사용)
await prisma.table_relationships.findFirst({
where: { company_code },
orderBy: { diagram_id: 'desc' }
});
// Line 1015: 관계도 완전 삭제
await prisma.table_relationships.deleteMany({
where: { company_code, diagram_id, is_active: 'Y' }
});
```
#### 1.5 복잡한 조회 (5개)
```typescript
// Line 919: 원본 관계도 조회
await prisma.table_relationships.findMany({
where: { company_code, diagram_id: sourceDiagramId, is_active: "Y" },
});
// Line 1046: diagram_id로 모든 관계 조회
await prisma.table_relationships.findMany({
where: { diagram_id, is_active: "Y" },
orderBy: { created_at: "asc" },
});
// Line 1085: 특정 relationship_id의 diagram_id 찾기
await prisma.table_relationships.findFirst({
where: { relationship_id, company_code },
});
```
### 2. 데이터 연결 브리지 (Data Relationship Bridge) - 8개
#### 2.1 브리지 생성/수정 (4개)
```typescript
// Line 425: 브리지 생성
await prisma.data_relationship_bridge.create({
data: {
relationship_id,
source_record_id,
target_record_id,
...
}
});
// Line 554: 브리지 수정
await prisma.data_relationship_bridge.update({
where: whereCondition,
data: { target_record_id, ... }
});
// Line 595: 브리지 소프트 삭제
await prisma.data_relationship_bridge.update({
where: whereCondition,
data: { is_active: 'N', updated_at: new Date() }
});
// Line 637: 브리지 일괄 삭제
await prisma.data_relationship_bridge.updateMany({
where: whereCondition,
data: { is_active: 'N', updated_at: new Date() }
});
```
#### 2.2 브리지 조회 (4개)
```typescript
// Line 471: relationship_id로 브리지 조회
await prisma.data_relationship_bridge.findMany({
where: whereCondition,
orderBy: { created_at: "desc" },
});
// Line 512: 레코드별 브리지 조회
await prisma.data_relationship_bridge.findMany({
where: whereCondition,
orderBy: { created_at: "desc" },
});
```
### 3. Raw Query 사용 (이미 있음) - 1개
```typescript
// Line 673: 테이블 존재 확인
await prisma.$queryRaw`
SELECT table_name
FROM information_schema.tables
WHERE table_schema = 'public'
AND table_name = ${tableName}
`;
```
### 4. 트랜잭션 사용 - 1개
```typescript
// Line 968: 관계도 복사 트랜잭션
await prisma.$transaction(
originalRelationships.map((rel) =>
prisma.table_relationships.create({
data: {
diagram_id: newDiagramId,
company_code: companyCode,
source_table: rel.source_table,
target_table: rel.target_table,
...
}
})
)
);
```
---
## 🛠️ 전환 전략
### 전략 1: 단계적 전환
1. **1단계**: 단순 CRUD 전환 (findFirst, findMany, create, update, delete)
2. **2단계**: 복잡한 조회 전환 (groupBy, count, 조건부 조회)
3. **3단계**: 트랜잭션 전환
4. **4단계**: Raw Query 개선
### 전략 2: 함수별 전환 우선순위
#### 🔴 최우선 (기본 CRUD)
- `createRelationship()` - Line 83
- `getRelationships()` - Line 128
- `getRelationshipById()` - Line 164
- `updateRelationship()` - Line 209
- `deleteRelationship()` - Line 248
#### 🟡 2순위 (브리지 관리)
- `createDataLink()` - Line 425
- `getLinkedData()` - Line 471
- `getLinkedDataByRecord()` - Line 512
- `updateDataLink()` - Line 554
- `deleteDataLink()` - Line 595
#### 🟢 3순위 (통계 & 조회)
- `getRelationshipStats()` - Line 362-376
- `getAllRelationshipsByCompany()` - Line 287
- `getRelationshipsByTable()` - Line 326
- `getDiagrams()` - Line 784
#### 🔵 4순위 (복잡한 기능)
- `copyDiagram()` - Line 968 (트랜잭션)
- `deleteDiagram()` - Line 1015
- `getRelationshipsForDiagram()` - Line 1046
---
## 📝 전환 예시
### 예시 1: createRelationship() 전환
**기존 Prisma 코드:**
```typescript
// Line 48: 최대 diagram_id 조회
const maxDiagramId = await prisma.table_relationships.findFirst({
where: { company_code: data.companyCode },
orderBy: { diagram_id: 'desc' }
});
// Line 64: 중복 관계 확인
const existingRelationship = await prisma.table_relationships.findFirst({
where: {
diagram_id: diagramId,
source_table: data.sourceTable,
target_table: data.targetTable,
relationship_type: data.relationshipType
}
});
// Line 83: 새 관계 생성
const relationship = await prisma.table_relationships.create({
data: {
diagram_id: diagramId,
company_code: data.companyCode,
diagram_name: data.diagramName,
source_table: data.sourceTable,
target_table: data.targetTable,
relationship_type: data.relationshipType,
...
}
});
```
**새로운 Raw Query 코드:**
```typescript
import { query } from "../database/db";
// 최대 diagram_id 조회
const maxDiagramResult = await query<{ diagram_id: number }>(
`SELECT diagram_id FROM table_relationships
WHERE company_code = $1
ORDER BY diagram_id DESC
LIMIT 1`,
[data.companyCode]
);
const diagramId =
data.diagramId ||
(maxDiagramResult.length > 0 ? maxDiagramResult[0].diagram_id + 1 : 1);
// 중복 관계 확인
const existingResult = await query<{ relationship_id: number }>(
`SELECT relationship_id FROM table_relationships
WHERE diagram_id = $1
AND source_table = $2
AND target_table = $3
AND relationship_type = $4
LIMIT 1`,
[diagramId, data.sourceTable, data.targetTable, data.relationshipType]
);
if (existingResult.length > 0) {
throw new Error("이미 존재하는 관계입니다.");
}
// 새 관계 생성
const [relationship] = await query<TableRelationship>(
`INSERT INTO table_relationships (
diagram_id, company_code, diagram_name, source_table, target_table,
relationship_type, connection_type, source_column, target_column,
is_active, created_at, updated_at
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, 'Y', NOW(), NOW())
RETURNING *`,
[
diagramId,
data.companyCode,
data.diagramName,
data.sourceTable,
data.targetTable,
data.relationshipType,
data.connectionType,
data.sourceColumn,
data.targetColumn,
]
);
```
### 예시 2: getRelationshipStats() 전환 (통계 조회)
**기존 Prisma 코드:**
```typescript
// Line 362: 전체 관계 수
const totalCount = await prisma.table_relationships.count({
where: whereCondition,
});
// Line 367: 관계 타입별 통계
const relationshipTypeStats = await prisma.table_relationships.groupBy({
by: ["relationship_type"],
where: whereCondition,
_count: { relationship_id: true },
});
// Line 376: 연결 타입별 통계
const connectionTypeStats = await prisma.table_relationships.groupBy({
by: ["connection_type"],
where: whereCondition,
_count: { relationship_id: true },
});
```
**새로운 Raw Query 코드:**
```typescript
// WHERE 조건 동적 생성
const whereParams: any[] = [];
let whereSQL = "";
let paramIndex = 1;
if (companyCode) {
whereSQL += `WHERE company_code = $${paramIndex}`;
whereParams.push(companyCode);
paramIndex++;
if (isActive !== undefined) {
whereSQL += ` AND is_active = $${paramIndex}`;
whereParams.push(isActive ? "Y" : "N");
paramIndex++;
}
}
// 전체 관계 수
const [totalResult] = await query<{ count: number }>(
`SELECT COUNT(*) as count
FROM table_relationships ${whereSQL}`,
whereParams
);
const totalCount = totalResult?.count || 0;
// 관계 타입별 통계
const relationshipTypeStats = await query<{
relationship_type: string;
count: number;
}>(
`SELECT relationship_type, COUNT(*) as count
FROM table_relationships ${whereSQL}
GROUP BY relationship_type
ORDER BY count DESC`,
whereParams
);
// 연결 타입별 통계
const connectionTypeStats = await query<{
connection_type: string;
count: number;
}>(
`SELECT connection_type, COUNT(*) as count
FROM table_relationships ${whereSQL}
GROUP BY connection_type
ORDER BY count DESC`,
whereParams
);
```
### 예시 3: copyDiagram() 트랜잭션 전환
**기존 Prisma 코드:**
```typescript
// Line 968: 트랜잭션으로 모든 관계 복사
const copiedRelationships = await prisma.$transaction(
originalRelationships.map((rel) =>
prisma.table_relationships.create({
data: {
diagram_id: newDiagramId,
company_code: companyCode,
diagram_name: newDiagramName,
source_table: rel.source_table,
target_table: rel.target_table,
...
}
})
)
);
```
**새로운 Raw Query 코드:**
```typescript
import { transaction } from "../database/db";
const copiedRelationships = await transaction(async (client) => {
const results: TableRelationship[] = [];
for (const rel of originalRelationships) {
const [copiedRel] = await client.query<TableRelationship>(
`INSERT INTO table_relationships (
diagram_id, company_code, diagram_name, source_table, target_table,
relationship_type, connection_type, source_column, target_column,
is_active, created_at, updated_at
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, 'Y', NOW(), NOW())
RETURNING *`,
[
newDiagramId,
companyCode,
newDiagramName,
rel.source_table,
rel.target_table,
rel.relationship_type,
rel.connection_type,
rel.source_column,
rel.target_column,
]
);
results.push(copiedRel);
}
return results;
});
```
---
## 🧪 테스트 계획
### 단위 테스트 (20개 이상)
```typescript
describe('DataflowService Raw Query 전환 테스트', () => {
describe('createRelationship', () => {
test('관계 생성 성공', async () => { ... });
test('중복 관계 에러', async () => { ... });
test('diagram_id 자동 생성', async () => { ... });
});
describe('getRelationships', () => {
test('전체 관계 조회 성공', async () => { ... });
test('회사별 필터링', async () => { ... });
test('diagram_id별 필터링', async () => { ... });
});
describe('getRelationshipStats', () => {
test('통계 조회 성공', async () => { ... });
test('관계 타입별 그룹화', async () => { ... });
test('연결 타입별 그룹화', async () => { ... });
});
describe('copyDiagram', () => {
test('관계도 복사 성공 (트랜잭션)', async () => { ... });
test('diagram_name 중복 에러', async () => { ... });
});
describe('createDataLink', () => {
test('데이터 연결 생성 성공', async () => { ... });
test('브리지 레코드 저장', async () => { ... });
});
describe('getLinkedData', () => {
test('연결된 데이터 조회', async () => { ... });
test('relationship_id별 필터링', async () => { ... });
});
});
```
### 통합 테스트 (7개 시나리오)
```typescript
describe('Dataflow 관리 통합 테스트', () => {
test('관계 생명주기 (생성 → 조회 → 수정 → 삭제)', async () => { ... });
test('관계도 복사 및 검증', async () => { ... });
test('데이터 연결 브리지 생성 및 조회', async () => { ... });
test('통계 정보 조회', async () => { ... });
test('테이블별 관계 조회', async () => { ... });
test('diagram_id별 관계 조회', async () => { ... });
test('관계도 완전 삭제', async () => { ... });
});
```
---
## 📋 체크리스트
### 1단계: 기본 CRUD (8개 함수) ⏳ **진행 예정**
- [ ] `createRelationship()` - 관계 생성
- [ ] `getRelationships()` - 관계 목록 조회
- [ ] `getRelationshipById()` - 단일 관계 조회
- [ ] `updateRelationship()` - 관계 수정
- [ ] `deleteRelationship()` - 관계 삭제 (소프트)
- [ ] `getAllRelationshipsByCompany()` - 회사별 전체 조회
- [ ] `getRelationshipsByTable()` - 테이블별 조회
- [ ] `getDiagrams()` - diagram_id별 그룹 조회
### 2단계: 브리지 관리 (6개 함수) ⏳ **진행 예정**
- [ ] `createDataLink()` - 데이터 연결 생성
- [ ] `getLinkedData()` - 연결 데이터 조회
- [ ] `getLinkedDataByRecord()` - 레코드별 연결 조회
- [ ] `updateDataLink()` - 연결 수정
- [ ] `deleteDataLink()` - 연결 삭제 (소프트)
- [ ] `bulkDeleteDataLinks()` - 연결 일괄 삭제
### 3단계: 통계 & 복잡한 조회 (3개 함수) ⏳ **진행 예정**
- [ ] `getRelationshipStats()` - 통계 조회
- [ ] count 쿼리 전환
- [ ] groupBy 쿼리 전환 (관계 타입별)
- [ ] groupBy 쿼리 전환 (연결 타입별)
- [ ] `getRelationshipsForDiagram()` - diagram_id별 관계 조회
- [ ] `checkTableExists()` - 테이블 존재 확인 (Raw Query 유지)
### 4단계: 복잡한 기능 (3개 함수) ⏳ **진행 예정**
- [ ] `copyDiagram()` - 관계도 복사 (트랜잭션)
- [ ] `deleteDiagram()` - 관계도 완전 삭제
- [ ] `getRelationshipIdByDiagramId()` - 유틸리티 메서드
### 5단계: 테스트 & 검증 ⏳ **진행 예정**
- [ ] 단위 테스트 작성 (20개 이상)
- createRelationship, updateRelationship, deleteRelationship
- getRelationships, getRelationshipById
- createDataLink, getLinkedData
- getRelationshipStats
- copyDiagram
- [ ] 통합 테스트 작성 (7개 시나리오)
- 관계 생명주기 테스트
- 관계도 복사 테스트
- 데이터 브리지 테스트
- 통계 조회 테스트
- [ ] Prisma import 완전 제거 확인
- [ ] 성능 테스트
---
## 🎯 완료 기준
- [ ] **31개 Prisma 호출 모두 Raw Query로 전환 완료**
- [ ] **모든 TypeScript 컴파일 오류 해결**
- [ ] **트랜잭션 정상 동작 확인**
- [ ] **에러 처리 및 롤백 정상 동작**
- [ ] **모든 단위 테스트 통과 (20개 이상)**
- [ ] **모든 통합 테스트 작성 완료 (7개 시나리오)**
- [ ] **Prisma import 완전 제거**
- [ ] **성능 저하 없음 (기존 대비 ±10% 이내)**
---
## 🎯 주요 기술적 도전 과제
### 1. groupBy 쿼리 전환
**문제**: Prisma의 `groupBy`를 Raw Query로 전환
**해결**: PostgreSQL의 `GROUP BY` 및 집계 함수 사용
```sql
SELECT relationship_type, COUNT(*) as count
FROM table_relationships
WHERE company_code = $1 AND is_active = 'Y'
GROUP BY relationship_type
ORDER BY count DESC
```
### 2. 트랜잭션 배열 처리
**문제**: Prisma의 `$transaction([...])` 배열 방식을 Raw Query로 전환
**해결**: `transaction` 함수 내에서 순차 실행
```typescript
await transaction(async (client) => {
const results = [];
for (const item of items) {
const result = await client.query(...);
results.push(result);
}
return results;
});
```
### 3. 동적 WHERE 조건 생성
**문제**: 다양한 필터 조건을 동적으로 구성
**해결**: 조건부 파라미터 인덱스 관리
```typescript
const whereParams: any[] = [];
const whereConditions: string[] = [];
let paramIndex = 1;
if (companyCode) {
whereConditions.push(`company_code = $${paramIndex++}`);
whereParams.push(companyCode);
}
if (diagramId) {
whereConditions.push(`diagram_id = $${paramIndex++}`);
whereParams.push(diagramId);
}
const whereSQL =
whereConditions.length > 0 ? `WHERE ${whereConditions.join(" AND ")}` : "";
```
---
**작성일**: 2025-09-30
**예상 소요 시간**: 2-3일
**담당자**: 백엔드 개발팀
**우선순위**: 🔴 최우선 (Phase 2.3)
**상태**: ⏳ **진행 예정**

View File

@ -0,0 +1,218 @@
# 📝 Phase 2.4: DynamicFormService Raw Query 전환 계획
## 📋 개요
DynamicFormService는 **13개의 Prisma 호출**이 있습니다. 대부분(약 11개)은 `$queryRaw`를 사용하고 있어 SQL은 이미 작성되어 있지만, **Prisma 클라이언트를 완전히 제거하려면 13개 모두를 `db.ts``query` 함수로 교체**해야 합니다.
### 📊 기본 정보
| 항목 | 내용 |
| --------------- | ---------------------------------------------------- |
| 파일 위치 | `backend-node/src/services/dynamicFormService.ts` |
| 파일 크기 | 1,200+ 라인 |
| Prisma 호출 | 13개 ($queryRaw: 11개, ORM: 2개) |
| **현재 진행률** | **0/13 (0%)****전환 필요** |
| **전환 필요** | **13개 모두 전환 필요** (SQL은 이미 작성되어 있음) |
| 복잡도 | 낮음 (SQL 작성은 완료, `query()` 함수로 교체만 필요) |
| 우선순위 | 🟢 낮음 (Phase 2.4) |
### 🎯 전환 목표
- ✅ **13개 모든 Prisma 호출을 `db.ts``query()` 함수로 교체**
- 11개 `$queryRaw``query()` 함수로 교체
- 2개 ORM 메서드 → `query()` (SQL 새로 작성)
- ✅ 모든 단위 테스트 통과
- ✅ **Prisma import 완전 제거**
---
## 🔍 Prisma 사용 현황 분석
### 1. `$queryRaw` / `$queryRawUnsafe` 사용 (11개)
**현재 상태**: SQL은 이미 작성되어 있음 ✅
**전환 작업**: `prisma.$queryRaw``query()` 함수로 교체만 하면 됨
```typescript
// 기존
await prisma.$queryRaw<Array<{ column_name; data_type }>>`...`;
await prisma.$queryRawUnsafe(upsertQuery, ...values);
// 전환 후
import { query } from "../database/db";
await query<Array<{ column_name: string; data_type: string }>>(`...`);
await query(upsertQuery, values);
```
### 2. ORM 메서드 사용 (2개)
**현재 상태**: Prisma ORM 메서드 사용
**전환 작업**: SQL 작성 필요
#### 1. dynamic_form_data 조회 (1개)
```typescript
// Line 867: 폼 데이터 조회
const result = await prisma.dynamic_form_data.findUnique({
where: { id },
select: { data: true },
});
```
#### 2. screen_layouts 조회 (1개)
```typescript
// Line 1101: 화면 레이아웃 조회
const screenLayouts = await prisma.screen_layouts.findMany({
where: {
screen_id: screenId,
component_type: "widget",
},
select: {
component_id: true,
properties: true,
},
});
```
---
## 📝 전환 예시
### 예시 1: dynamic_form_data 조회 전환
**기존 Prisma 코드:**
```typescript
const result = await prisma.dynamic_form_data.findUnique({
where: { id },
select: { data: true },
});
```
**새로운 Raw Query 코드:**
```typescript
import { queryOne } from "../database/db";
const result = await queryOne<{ data: any }>(
`SELECT data FROM dynamic_form_data WHERE id = $1`,
[id]
);
```
### 예시 2: screen_layouts 조회 전환
**기존 Prisma 코드:**
```typescript
const screenLayouts = await prisma.screen_layouts.findMany({
where: {
screen_id: screenId,
component_type: "widget",
},
select: {
component_id: true,
properties: true,
},
});
```
**새로운 Raw Query 코드:**
```typescript
import { query } from "../database/db";
const screenLayouts = await query<{
component_id: string;
properties: any;
}>(
`SELECT component_id, properties
FROM screen_layouts
WHERE screen_id = $1 AND component_type = $2`,
[screenId, "widget"]
);
```
---
## 🧪 테스트 계획
### 단위 테스트 (5개)
```typescript
describe("DynamicFormService Raw Query 전환 테스트", () => {
describe("getFormDataById", () => {
test("폼 데이터 조회 성공", async () => { ... });
test("존재하지 않는 데이터", async () => { ... });
});
describe("getScreenLayoutsForControl", () => {
test("화면 레이아웃 조회 성공", async () => { ... });
test("widget 타입만 필터링", async () => { ... });
test("빈 결과 처리", async () => { ... });
});
});
```
### 통합 테스트 (3개 시나리오)
```typescript
describe("동적 폼 통합 테스트", () => {
test("폼 데이터 UPSERT → 조회", async () => { ... });
test("폼 데이터 업데이트 → 조회", async () => { ... });
test("화면 레이아웃 조회 → 제어 설정 확인", async () => { ... });
});
```
---
## 📋 체크리스트
### 1단계: ORM 호출 전환 (2개 함수) ⏳ **진행 예정**
- [ ] `getFormDataById()` - dynamic_form_data.findUnique
- [ ] `getScreenLayoutsForControl()` - screen_layouts.findMany
### 2단계: 테스트 & 검증 ⏳ **진행 예정**
- [ ] 단위 테스트 작성 (5개)
- [ ] 통합 테스트 작성 (3개 시나리오)
- [ ] Prisma import 완전 제거 확인
- [ ] 성능 테스트
---
## 🎯 완료 기준
- [ ] **13개 모든 Prisma 호출을 Raw Query로 전환 완료**
- [ ] 11개 `$queryRaw``query()` 함수로 교체
- [ ] 2개 ORM 메서드 → `query()` 함수로 전환 (SQL 작성)
- [ ] **모든 TypeScript 컴파일 오류 해결**
- [ ] **모든 단위 테스트 통과 (5개)**
- [ ] **모든 통합 테스트 작성 완료 (3개 시나리오)**
- [ ] **`import prisma` 완전 제거 및 `import { query } from "../database/db"` 사용**
- [ ] **성능 저하 없음**
---
## 💡 특이사항
### SQL은 이미 거의 작성되어 있음
이 서비스는 이미 85%가 `$queryRaw`를 사용하고 있어, **SQL 작성은 거의 완료**되었습니다:
- ✅ UPSERT 로직: SQL 작성 완료 (`$queryRawUnsafe` 사용 중)
- ✅ 컬럼 정보 조회: SQL 작성 완료 (`$queryRaw` 사용 중)
- ✅ Primary Key 조회: SQL 작성 완료 (Raw Query 사용 중)
- ⏳ **전환 작업**: `prisma.$queryRaw``query()` 함수로 **단순 교체만 필요**
- ⏳ 단순 조회: 2개만 SQL 새로 작성 필요 (매우 간단한 SELECT 쿼리)
---
**작성일**: 2025-09-30
**예상 소요 시간**: 0.5일 (SQL은 85% 작성 완료, 함수 교체 작업 필요)
**담당자**: 백엔드 개발팀
**우선순위**: 🟢 낮음 (Phase 2.4)
**상태**: ⏳ **진행 예정**
**특이사항**: SQL은 거의 작성되어 있어 `prisma.$queryRaw``query()` 단순 교체 작업이 주요 작업

View File

@ -0,0 +1,99 @@
# 🔌 Phase 2.5: ExternalDbConnectionService Raw Query 전환 계획
## 📋 개요
ExternalDbConnectionService는 **15개의 Prisma 호출**이 있으며, 외부 데이터베이스 연결 정보를 관리하는 서비스입니다.
### 📊 기본 정보
| 항목 | 내용 |
| --------------- | ---------------------------------------------------------- |
| 파일 위치 | `backend-node/src/services/externalDbConnectionService.ts` |
| 파일 크기 | 800+ 라인 |
| Prisma 호출 | 15개 |
| **현재 진행률** | **0/15 (0%)****진행 예정** |
| 복잡도 | 중간 (CRUD + 연결 테스트) |
| 우선순위 | 🟡 중간 (Phase 2.5) |
### 🎯 전환 목표
- ✅ 15개 Prisma 호출을 모두 Raw Query로 전환
- ✅ 민감 정보 암호화 처리 유지
- ✅ 연결 테스트 로직 정상 동작
- ✅ 모든 단위 테스트 통과
---
## 🔍 주요 기능
### 1. 외부 DB 연결 정보 CRUD
- 생성, 조회, 수정, 삭제
- 연결 정보 암호화/복호화
### 2. 연결 테스트
- MySQL, PostgreSQL, MSSQL, Oracle 연결 테스트
### 3. 연결 정보 관리
- 회사별 연결 정보 조회
- 활성/비활성 상태 관리
---
## 📝 예상 전환 패턴
### CRUD 작업
```typescript
// 생성
await query(
`INSERT INTO external_db_connections
(connection_name, db_type, host, port, database_name, username, password, company_code)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
RETURNING *`,
[...]
);
// 조회
await query(
`SELECT * FROM external_db_connections
WHERE company_code = $1 AND is_active = 'Y'`,
[companyCode]
);
// 수정
await query(
`UPDATE external_db_connections
SET connection_name = $1, host = $2, ...
WHERE connection_id = $2`,
[...]
);
// 삭제 (소프트)
await query(
`UPDATE external_db_connections
SET is_active = 'N'
WHERE connection_id = $1`,
[connectionId]
);
```
---
## 🎯 완료 기준
- [ ] **15개 Prisma 호출 모두 Raw Query로 전환**
- [ ] **암호화/복호화 로직 정상 동작**
- [ ] **연결 테스트 정상 동작**
- [ ] **모든 단위 테스트 통과 (10개 이상)**
- [ ] **Prisma import 완전 제거**
---
**작성일**: 2025-09-30
**예상 소요 시간**: 1일
**담당자**: 백엔드 개발팀
**우선순위**: 🟡 중간 (Phase 2.5)
**상태**: ⏳ **진행 예정**

View File

@ -0,0 +1,205 @@
# 🎮 Phase 2.6: DataflowControlService Raw Query 전환 계획
## 📋 개요
DataflowControlService는 **6개의 Prisma 호출**이 있으며, 데이터플로우 제어 및 실행을 담당하는 서비스입니다.
### 📊 기본 정보
| 항목 | 내용 |
| --------------- | ----------------------------------------------------- |
| 파일 위치 | `backend-node/src/services/dataflowControlService.ts` |
| 파일 크기 | 600+ 라인 |
| Prisma 호출 | 6개 |
| **현재 진행률** | **0/6 (0%)****진행 예정** |
| 복잡도 | 높음 (복잡한 비즈니스 로직) |
| 우선순위 | 🟡 중간 (Phase 2.6) |
### 🎯 전환 목표
- ✅ **6개 모든 Prisma 호출을 `db.ts``query()` 함수로 교체**
- ✅ 복잡한 비즈니스 로직 정상 동작 확인
- ✅ 모든 단위 테스트 통과
- ✅ **Prisma import 완전 제거**
---
## 🔍 Prisma 사용 현황 분석
### 주요 기능
1. **데이터플로우 실행 관리**
- 관계 기반 데이터 조회 및 저장
- 조건부 실행 로직
2. **트랜잭션 처리**
- 여러 테이블에 걸친 데이터 처리
3. **데이터 변환 및 매핑**
- 소스-타겟 데이터 변환
---
## 📝 전환 계획
### 1단계: 기본 조회 전환 (2개 함수)
**함수 목록**:
- `getRelationshipById()` - 관계 정보 조회
- `getDataflowConfig()` - 데이터플로우 설정 조회
### 2단계: 데이터 실행 로직 전환 (2개 함수)
**함수 목록**:
- `executeDataflow()` - 데이터플로우 실행
- `validateDataflow()` - 데이터플로우 검증
### 3단계: 복잡한 기능 - 트랜잭션 (2개 함수)
**함수 목록**:
- `executeWithTransaction()` - 트랜잭션 내 실행
- `rollbackOnError()` - 에러 시 롤백
---
## 💻 전환 예시
### 예시 1: 관계 정보 조회
```typescript
// 기존 Prisma
const relationship = await prisma.table_relationship.findUnique({
where: { relationship_id: relationshipId },
include: {
source_table: true,
target_table: true,
},
});
// 전환 후
import { query } from "../database/db";
const relationship = await query<TableRelationship>(
`SELECT
tr.*,
st.table_name as source_table_name,
tt.table_name as target_table_name
FROM table_relationship tr
LEFT JOIN table_labels st ON tr.source_table_id = st.table_id
LEFT JOIN table_labels tt ON tr.target_table_id = tt.table_id
WHERE tr.relationship_id = $1`,
[relationshipId]
);
```
### 예시 2: 트랜잭션 내 실행
```typescript
// 기존 Prisma
await prisma.$transaction(async (tx) => {
// 소스 데이터 조회
const sourceData = await tx.dynamic_form_data.findMany(...);
// 타겟 데이터 저장
await tx.dynamic_form_data.createMany(...);
// 실행 로그 저장
await tx.dataflow_execution_log.create(...);
});
// 전환 후
import { transaction } from "../database/db";
await transaction(async (client) => {
// 소스 데이터 조회
const sourceData = await client.query(
`SELECT * FROM dynamic_form_data WHERE ...`,
[...]
);
// 타겟 데이터 저장
await client.query(
`INSERT INTO dynamic_form_data (...) VALUES (...)`,
[...]
);
// 실행 로그 저장
await client.query(
`INSERT INTO dataflow_execution_log (...) VALUES (...)`,
[...]
);
});
```
---
## ✅ 5단계: 테스트 & 검증
### 단위 테스트 (10개)
- [ ] getRelationshipById - 관계 정보 조회
- [ ] getDataflowConfig - 설정 조회
- [ ] executeDataflow - 데이터플로우 실행
- [ ] validateDataflow - 검증
- [ ] executeWithTransaction - 트랜잭션 실행
- [ ] rollbackOnError - 에러 처리
- [ ] transformData - 데이터 변환
- [ ] mapSourceToTarget - 필드 매핑
- [ ] applyConditions - 조건 적용
- [ ] logExecution - 실행 로그
### 통합 테스트 (4개 시나리오)
1. **데이터플로우 실행 시나리오**
- 관계 조회 → 데이터 실행 → 로그 저장
2. **트랜잭션 테스트**
- 여러 테이블 동시 처리
- 에러 발생 시 롤백
3. **조건부 실행 테스트**
- 조건에 따른 데이터 처리
4. **데이터 변환 테스트**
- 소스-타겟 데이터 매핑
---
## 🎯 완료 기준
- [ ] **6개 모든 Prisma 호출을 Raw Query로 전환 완료**
- [ ] **모든 TypeScript 컴파일 오류 해결**
- [ ] **트랜잭션 정상 동작 확인**
- [ ] **복잡한 비즈니스 로직 정상 동작**
- [ ] **모든 단위 테스트 통과 (10개)**
- [ ] **모든 통합 테스트 작성 완료 (4개 시나리오)**
- [ ] **`import prisma` 완전 제거 및 `import { query, transaction } from "../database/db"` 사용**
- [ ] **성능 저하 없음**
---
## 💡 특이사항
### 복잡한 비즈니스 로직
이 서비스는 데이터플로우 제어라는 복잡한 비즈니스 로직을 처리합니다:
- 조건부 실행 로직
- 데이터 변환 및 매핑
- 트랜잭션 관리
- 에러 처리 및 롤백
### 성능 최적화 중요
데이터플로우 실행은 대량의 데이터를 처리할 수 있으므로:
- 배치 처리 고려
- 인덱스 활용
- 쿼리 최적화
---
**작성일**: 2025-09-30
**예상 소요 시간**: 1일
**담당자**: 백엔드 개발팀
**우선순위**: 🟡 중간 (Phase 2.6)
**상태**: ⏳ **진행 예정**
**특이사항**: 복잡한 비즈니스 로직이 포함되어 있어 신중한 테스트 필요

View File

@ -0,0 +1,175 @@
# 🔧 Phase 2.7: DDLExecutionService Raw Query 전환 계획
## 📋 개요
DDLExecutionService는 **4개의 Prisma 호출**이 있으며, DDL(Data Definition Language) 실행 및 관리를 담당하는 서비스입니다.
### 📊 기본 정보
| 항목 | 내용 |
| --------------- | -------------------------------------------------- |
| 파일 위치 | `backend-node/src/services/ddlExecutionService.ts` |
| 파일 크기 | 400+ 라인 |
| Prisma 호출 | 4개 |
| **현재 진행률** | **0/4 (0%)****진행 예정** |
| 복잡도 | 중간 (DDL 실행 + 로그 관리) |
| 우선순위 | 🟢 낮음 (Phase 2.7) |
### 🎯 전환 목표
- ✅ **4개 모든 Prisma 호출을 `db.ts``query()` 함수로 교체**
- ✅ DDL 실행 정상 동작 확인
- ✅ 모든 단위 테스트 통과
- ✅ **Prisma import 완전 제거**
---
## 🔍 Prisma 사용 현황 분석
### 주요 기능
1. **DDL 실행**
- CREATE TABLE, ALTER TABLE, DROP TABLE
- CREATE INDEX, DROP INDEX
2. **실행 로그 관리**
- DDL 실행 이력 저장
- 에러 로그 관리
3. **롤백 지원**
- DDL 롤백 SQL 생성 및 실행
---
## 📝 전환 계획
### 1단계: DDL 실행 전환 (2개 함수)
**함수 목록**:
- `executeDDL()` - DDL 실행
- `validateDDL()` - DDL 문법 검증
### 2단계: 로그 관리 전환 (2개 함수)
**함수 목록**:
- `saveDDLLog()` - 실행 로그 저장
- `getDDLHistory()` - 실행 이력 조회
---
## 💻 전환 예시
### 예시 1: DDL 실행 및 로그 저장
```typescript
// 기존 Prisma
await prisma.$executeRawUnsafe(ddlQuery);
await prisma.ddl_execution_log.create({
data: {
ddl_statement: ddlQuery,
execution_status: "SUCCESS",
executed_by: userId,
},
});
// 전환 후
import { query } from "../database/db";
await query(ddlQuery);
await query(
`INSERT INTO ddl_execution_log
(ddl_statement, execution_status, executed_by, executed_date)
VALUES ($1, $2, $3, $4)`,
[ddlQuery, "SUCCESS", userId, new Date()]
);
```
### 예시 2: DDL 실행 이력 조회
```typescript
// 기존 Prisma
const history = await prisma.ddl_execution_log.findMany({
where: {
company_code: companyCode,
execution_status: "SUCCESS",
},
orderBy: { executed_date: "desc" },
take: 50,
});
// 전환 후
import { query } from "../database/db";
const history = await query<DDLLog[]>(
`SELECT * FROM ddl_execution_log
WHERE company_code = $1
AND execution_status = $2
ORDER BY executed_date DESC
LIMIT $3`,
[companyCode, "SUCCESS", 50]
);
```
---
## ✅ 3단계: 테스트 & 검증
### 단위 테스트 (8개)
- [ ] executeDDL - CREATE TABLE
- [ ] executeDDL - ALTER TABLE
- [ ] executeDDL - DROP TABLE
- [ ] executeDDL - CREATE INDEX
- [ ] validateDDL - 문법 검증
- [ ] saveDDLLog - 로그 저장
- [ ] getDDLHistory - 이력 조회
- [ ] rollbackDDL - DDL 롤백
### 통합 테스트 (3개 시나리오)
1. **테이블 생성 → 로그 저장 → 이력 조회**
2. **DDL 실행 실패 → 에러 로그 저장**
3. **DDL 롤백 테스트**
---
## 🎯 완료 기준
- [ ] **4개 모든 Prisma 호출을 Raw Query로 전환 완료**
- [ ] **모든 TypeScript 컴파일 오류 해결**
- [ ] **DDL 실행 정상 동작 확인**
- [ ] **모든 단위 테스트 통과 (8개)**
- [ ] **모든 통합 테스트 작성 완료 (3개 시나리오)**
- [ ] **`import prisma` 완전 제거 및 `import { query } from "../database/db"` 사용**
- [ ] **성능 저하 없음**
---
## 💡 특이사항
### DDL 실행의 위험성
DDL은 데이터베이스 스키마를 변경하므로 매우 신중하게 처리해야 합니다:
- 실행 전 검증 필수
- 롤백 SQL 자동 생성
- 실행 이력 철저히 관리
### 트랜잭션 지원 제한
PostgreSQL에서 일부 DDL은 트랜잭션을 지원하지만, 일부는 자동 커밋됩니다:
- CREATE TABLE: 트랜잭션 지원 ✅
- DROP TABLE: 트랜잭션 지원 ✅
- CREATE INDEX CONCURRENTLY: 트랜잭션 미지원 ❌
---
**작성일**: 2025-09-30
**예상 소요 시간**: 0.5일
**담당자**: 백엔드 개발팀
**우선순위**: 🟢 낮음 (Phase 2.7)
**상태**: ⏳ **진행 예정**
**특이사항**: DDL 실행의 특성상 신중한 테스트 필요

View File

@ -1065,16 +1065,42 @@ describe("Performance Benchmarks", () => {
### **Phase 2: 핵심 서비스 (3주) - 107개 호출**
- [ ] ScreenManagementService 전환 (46개) - 최우선
- [ ] TableManagementService 전환 (35개) - 최우선
- [ ] DataflowService 전환 (31개) ⭐ 신규 발견
- [ ] DynamicFormService 전환 (15개) - UPSERT 포함
- [ ] ExternalDbConnectionService 전환 (15개)
- [ ] DataflowControlService 전환 (6개) - 복잡한 로직
- [ ] DDLExecutionService 전환 (6개)
- [ ] ~~AuthService 전환 (5개)~~ ✅ Phase 1.5로 이동
- [ ] ~~MultiConnectionQueryService 전환 (4개)~~ ✅ Phase 1 완료
- [ ] 통합 테스트 실행
#### ✅ 완료된 서비스
- [x] **ScreenManagementService 전환 (46개)** ✅ **완료**
- [x] 46개 Prisma 호출 전환 완료
- [x] 18개 단위 테스트 통과
- [x] 6개 통합 테스트 작성 완료
- [x] 실제 운영 버그 발견 및 수정 (소수점 좌표)
- 📄 **[PHASE2_SCREEN_MANAGEMENT_MIGRATION.md](PHASE2_SCREEN_MANAGEMENT_MIGRATION.md)**
#### ⏳ 진행 예정 서비스
- [ ] **TableManagementService 전환 (33개)** - Phase 2.2 🟡 중간 우선순위
- 33개 Prisma 호출 ($queryRaw 26개 + ORM 7개)
- SQL은 79% 작성 완료 → `query()` 함수로 교체만 필요
- 📄 **[PHASE2.2_TABLE_MANAGEMENT_MIGRATION.md](PHASE2.2_TABLE_MANAGEMENT_MIGRATION.md)**
- [ ] **DataflowService 전환 (31개)** - Phase 2.3 🔴 최우선
- 31개 Prisma 호출 (복잡한 관계 관리)
- 📄 **[PHASE2.3_DATAFLOW_SERVICE_MIGRATION.md](PHASE2.3_DATAFLOW_SERVICE_MIGRATION.md)**
- [ ] **DynamicFormService 전환 (13개)** - Phase 2.4 🟢 낮은 우선순위
- 13개 Prisma 호출 ($queryRaw 11개 + ORM 2개)
- SQL은 85% 작성 완료 → `query()` 함수로 교체만 필요
- 📄 **[PHASE2.4_DYNAMIC_FORM_MIGRATION.md](PHASE2.4_DYNAMIC_FORM_MIGRATION.md)**
- [ ] **ExternalDbConnectionService 전환 (15개)** - Phase 2.5 🟡 중간 우선순위
- 15개 Prisma 호출 (외부 DB 연결 관리)
- 📄 **[PHASE2.5_EXTERNAL_DB_CONNECTION_MIGRATION.md](PHASE2.5_EXTERNAL_DB_CONNECTION_MIGRATION.md)**
- [ ] **DataflowControlService 전환 (6개)** - Phase 2.6 🟡 중간 우선순위
- 6개 Prisma 호출 (복잡한 비즈니스 로직)
- 📄 **[PHASE2.6_DATAFLOW_CONTROL_MIGRATION.md](PHASE2.6_DATAFLOW_CONTROL_MIGRATION.md)**
- [ ] **DDLExecutionService 전환 (4개)** - Phase 2.7 🟢 낮은 우선순위
- 4개 Prisma 호출 (DDL 실행 및 로그)
- 📄 **[PHASE2.7_DDL_EXECUTION_MIGRATION.md](PHASE2.7_DDL_EXECUTION_MIGRATION.md)**
#### ✅ 다른 Phase로 이동
- [x] ~~AuthService 전환 (5개)~~ → Phase 1.5로 이동
- [x] ~~MultiConnectionQueryService 전환 (4개)~~ → Phase 1 완료
### **Phase 3: 관리 기능 (2.5주) - 162개 호출**