From ba10e7a12b12b376c78de7a2aafdc1f34308ba20 Mon Sep 17 00:00:00 2001 From: kjs Date: Tue, 30 Sep 2025 16:07:31 +0900 Subject: [PATCH] docs: Add Phase 2.1 ScreenManagementService migration plan MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Phase 2 시작: ScreenManagementService 상세 분석 완료 📊 분석 결과: - 총 46개 Prisma 호출 (Phase 2 최대) - 파일: 1,805 라인 - 복잡도: 매우 높음 🔍 Prisma 사용: - 화면 정의: 18개 - 레이아웃: 4개 - 템플릿: 2개 - 메뉴 할당: 5개 - 트랜잭션: 3개 📋 4단계 전환 계획: 1. 기본 CRUD (6함수) 2. 레이아웃 (3함수) 3. 템플릿&메뉴 (6함수) 4. 복잡한 기능 (4함수) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- PHASE2_SCREEN_MANAGEMENT_MIGRATION.md | 449 ++++++++++++++++++++++++++ 1 file changed, 449 insertions(+) create mode 100644 PHASE2_SCREEN_MANAGEMENT_MIGRATION.md diff --git a/PHASE2_SCREEN_MANAGEMENT_MIGRATION.md b/PHASE2_SCREEN_MANAGEMENT_MIGRATION.md new file mode 100644 index 00000000..c54f2966 --- /dev/null +++ b/PHASE2_SCREEN_MANAGEMENT_MIGRATION.md @@ -0,0 +1,449 @@ +# 🖥️ Phase 2.1: ScreenManagementService Raw Query 전환 계획 + +## 📋 개요 + +ScreenManagementService는 **46개의 Prisma 호출**이 있는 가장 복잡한 서비스입니다. 화면 정의, 레이아웃, 메뉴 할당, 템플릿 등 다양한 기능을 포함합니다. + +### 📊 기본 정보 + +| 항목 | 내용 | +|------|------| +| 파일 위치 | `backend-node/src/services/screenManagementService.ts` | +| 파일 크기 | 1,700+ 라인 | +| Prisma 호출 | 46개 | +| 복잡도 | 매우 높음 | +| 우선순위 | 🔴 최우선 | + +--- + +## 🔍 Prisma 사용 현황 분석 + +### 1. 화면 정의 관리 (Screen Definitions) - 18개 + +```typescript +// Line 53: 화면 코드 중복 확인 +await prisma.screen_definitions.findFirst({ where: { screen_code, is_active: { not: "D" } } }) + +// Line 70: 화면 생성 +await prisma.screen_definitions.create({ data: { ... } }) + +// Line 99: 화면 목록 조회 (페이징) +await prisma.screen_definitions.findMany({ where, skip, take, orderBy }) + +// Line 105: 화면 총 개수 +await prisma.screen_definitions.count({ where }) + +// Line 166: 전체 화면 목록 +await prisma.screen_definitions.findMany({ where }) + +// Line 178: 화면 코드로 조회 +await prisma.screen_definitions.findFirst({ where: { screen_code } }) + +// Line 205: 화면 ID로 조회 +await prisma.screen_definitions.findFirst({ where: { screen_id } }) + +// Line 221: 화면 존재 확인 +await prisma.screen_definitions.findUnique({ where: { screen_id } }) + +// Line 236: 화면 업데이트 +await prisma.screen_definitions.update({ where, data }) + +// Line 268: 화면 복사 - 원본 조회 +await prisma.screen_definitions.findUnique({ where, include: { screen_layouts } }) + +// Line 292: 화면 순서 변경 - 전체 조회 +await prisma.screen_definitions.findMany({ where }) + +// Line 486: 화면 템플릿 적용 - 존재 확인 +await prisma.screen_definitions.findUnique({ where }) + +// Line 557: 화면 복사 - 존재 확인 +await prisma.screen_definitions.findUnique({ where }) + +// Line 578: 화면 복사 - 중복 확인 +await prisma.screen_definitions.findFirst({ where }) + +// Line 651: 화면 삭제 - 존재 확인 +await prisma.screen_definitions.findUnique({ where }) + +// Line 672: 화면 삭제 (물리 삭제) +await prisma.screen_definitions.delete({ where }) + +// Line 700: 삭제된 화면 조회 +await prisma.screen_definitions.findMany({ where: { is_active: "D" } }) + +// Line 706: 삭제된 화면 개수 +await prisma.screen_definitions.count({ where }) + +// Line 763: 일괄 삭제 - 화면 조회 +await prisma.screen_definitions.findMany({ where }) + +// Line 1083: 레이아웃 저장 - 화면 확인 +await prisma.screen_definitions.findUnique({ where }) + +// Line 1181: 레이아웃 조회 - 화면 확인 +await prisma.screen_definitions.findUnique({ where }) + +// Line 1655: 위젯 데이터 저장 - 화면 존재 확인 +await prisma.screen_definitions.findMany({ where }) +``` + +### 2. 레이아웃 관리 (Screen Layouts) - 4개 + +```typescript +// Line 1096: 레이아웃 삭제 +await prisma.screen_layouts.deleteMany({ where: { screen_id } }) + +// Line 1107: 레이아웃 생성 (단일) +await prisma.screen_layouts.create({ data }) + +// Line 1152: 레이아웃 생성 (다중) +await prisma.screen_layouts.create({ data }) + +// Line 1193: 레이아웃 조회 +await prisma.screen_layouts.findMany({ where }) +``` + +### 3. 템플릿 관리 (Screen Templates) - 2개 + +```typescript +// Line 1303: 템플릿 목록 조회 +await prisma.screen_templates.findMany({ where }) + +// Line 1317: 템플릿 생성 +await prisma.screen_templates.create({ data }) +``` + +### 4. 메뉴 할당 (Screen Menu Assignments) - 5개 + +```typescript +// Line 446: 메뉴 할당 조회 +await prisma.screen_menu_assignments.findMany({ where }) + +// Line 1346: 메뉴 할당 중복 확인 +await prisma.screen_menu_assignments.findFirst({ where }) + +// Line 1358: 메뉴 할당 생성 +await prisma.screen_menu_assignments.create({ data }) + +// Line 1376: 화면별 메뉴 할당 조회 +await prisma.screen_menu_assignments.findMany({ where }) + +// Line 1401: 메뉴 할당 삭제 +await prisma.screen_menu_assignments.deleteMany({ where }) +``` + +### 5. 테이블 레이블 (Table Labels) - 3개 + +```typescript +// Line 117: 테이블 레이블 조회 (페이징) +await prisma.table_labels.findMany({ where, skip, take }) + +// Line 713: 테이블 레이블 조회 (전체) +await prisma.table_labels.findMany({ where }) +``` + +### 6. 컬럼 레이블 (Column Labels) - 2개 + +```typescript +// Line 948: 웹타입 정보 조회 +await prisma.column_labels.findMany({ where, select }) + +// Line 1456: 컬럼 레이블 UPSERT +await prisma.column_labels.upsert({ where, create, update }) +``` + +### 7. Raw Query 사용 (이미 있음) - 6개 + +```typescript +// Line 627: 화면 순서 변경 (일괄 업데이트) +await prisma.$executeRaw`UPDATE screen_definitions SET display_order = ...` + +// Line 833: 테이블 목록 조회 +await prisma.$queryRaw>`SELECT tablename ...` + +// Line 876: 테이블 존재 확인 +await prisma.$queryRaw>`SELECT tablename ...` + +// Line 922: 테이블 컬럼 정보 조회 +await prisma.$queryRaw>`SELECT column_name, data_type ...` + +// Line 1418: 컬럼 정보 조회 (상세) +await prisma.$queryRaw`SELECT column_name, data_type ...` +``` + +### 8. 트랜잭션 사용 - 3개 + +```typescript +// Line 521: 화면 템플릿 적용 트랜잭션 +await prisma.$transaction(async (tx) => { ... }) + +// Line 593: 화면 복사 트랜잭션 +await prisma.$transaction(async (tx) => { ... }) + +// Line 788: 일괄 삭제 트랜잭션 +await prisma.$transaction(async (tx) => { ... }) + +// Line 1697: 위젯 데이터 저장 트랜잭션 +await prisma.$transaction(async (tx) => { ... }) +``` + +--- + +## 🛠️ 전환 전략 + +### 전략 1: 단계적 전환 + +1. **1단계**: 단순 CRUD 전환 (findFirst, findMany, create, update, delete) +2. **2단계**: 복잡한 조회 전환 (include, join) +3. **3단계**: 트랜잭션 전환 +4. **4단계**: Raw Query 개선 + +### 전략 2: 함수별 전환 우선순위 + +#### 🔴 최우선 (기본 CRUD) +- `createScreen()` - Line 70 +- `getScreensByCompany()` - Line 99-105 +- `getScreenByCode()` - Line 178 +- `getScreenById()` - Line 205 +- `updateScreen()` - Line 236 +- `deleteScreen()` - Line 672 + +#### 🟡 2순위 (레이아웃) +- `saveLayout()` - Line 1096-1152 +- `getLayout()` - Line 1193 +- `deleteLayout()` - Line 1096 + +#### 🟢 3순위 (템플릿 & 메뉴) +- `getTemplates()` - Line 1303 +- `createTemplate()` - Line 1317 +- `assignToMenu()` - Line 1358 +- `getMenuAssignments()` - Line 1376 +- `removeMenuAssignment()` - Line 1401 + +#### 🔵 4순위 (복잡한 기능) +- `copyScreen()` - Line 593 (트랜잭션) +- `applyTemplate()` - Line 521 (트랜잭션) +- `bulkDelete()` - Line 788 (트랜잭션) +- `reorderScreens()` - Line 627 (Raw Query) + +--- + +## 📝 전환 예시 + +### 예시 1: createScreen() 전환 + +**기존 Prisma 코드:** +```typescript +// Line 53: 중복 확인 +const existingScreen = await prisma.screen_definitions.findFirst({ + where: { + screen_code: screenData.screenCode, + is_active: { not: "D" }, + }, +}); + +// Line 70: 생성 +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 코드:** +```typescript +import { query } from "../database/db"; + +// 중복 확인 +const existingResult = await query<{ screen_id: number }>( + `SELECT screen_id FROM screen_definitions + WHERE screen_code = $1 AND is_active != 'D' + LIMIT 1`, + [screenData.screenCode] +); + +if (existingResult.length > 0) { + throw new Error("이미 존재하는 화면 코드입니다."); +} + +// 생성 +const [screen] = await query( + `INSERT INTO screen_definitions ( + screen_name, screen_code, table_name, company_code, description, created_by + ) VALUES ($1, $2, $3, $4, $5, $6) + RETURNING *`, + [ + screenData.screenName, + screenData.screenCode, + screenData.tableName, + screenData.companyCode, + screenData.description, + screenData.createdBy, + ] +); +``` + +### 예시 2: getScreensByCompany() 전환 (페이징) + +**기존 Prisma 코드:** +```typescript +const [screens, total] = await Promise.all([ + prisma.screen_definitions.findMany({ + where: whereClause, + skip: (page - 1) * size, + take: size, + orderBy: { created_at: "desc" }, + }), + prisma.screen_definitions.count({ where: whereClause }), +]); +``` + +**새로운 Raw Query 코드:** +```typescript +const offset = (page - 1) * size; +const whereSQL = companyCode !== "*" + ? "WHERE company_code = $1 AND is_active != 'D'" + : "WHERE is_active != 'D'"; +const params = companyCode !== "*" ? [companyCode, size, offset] : [size, offset]; + +const [screens, totalResult] = await Promise.all([ + query( + `SELECT * FROM screen_definitions + ${whereSQL} + ORDER BY created_at DESC + LIMIT $${params.length - 1} OFFSET $${params.length}`, + params + ), + query<{ count: number }>( + `SELECT COUNT(*) as count FROM screen_definitions ${whereSQL}`, + companyCode !== "*" ? [companyCode] : [] + ), +]); + +const total = totalResult[0]?.count || 0; +``` + +### 예시 3: 트랜잭션 전환 + +**기존 Prisma 코드:** +```typescript +await prisma.$transaction(async (tx) => { + const newScreen = await tx.screen_definitions.create({ data: { ... } }); + await tx.screen_layouts.createMany({ data: layouts }); +}); +``` + +**새로운 Raw Query 코드:** +```typescript +import { transaction } from "../database/db"; + +await transaction(async (client) => { + const [newScreen] = await client.query( + `INSERT INTO screen_definitions (...) VALUES (...) RETURNING *`, + [...] + ); + + for (const layout of layouts) { + await client.query( + `INSERT INTO screen_layouts (...) VALUES (...)`, + [...] + ); + } +}); +``` + +--- + +## 🧪 테스트 계획 + +### 단위 테스트 + +```typescript +describe("ScreenManagementService Raw Query 전환 테스트", () => { + describe("createScreen", () => { + test("화면 생성 성공", async () => { ... }); + test("중복 화면 코드 에러", async () => { ... }); + }); + + describe("getScreensByCompany", () => { + test("페이징 조회 성공", async () => { ... }); + test("회사별 필터링", async () => { ... }); + }); + + describe("copyScreen", () => { + test("화면 복사 성공 (트랜잭션)", async () => { ... }); + test("레이아웃 함께 복사", async () => { ... }); + }); +}); +``` + +### 통합 테스트 + +```typescript +describe("화면 관리 통합 테스트", () => { + test("화면 생성 → 조회 → 수정 → 삭제", async () => { ... }); + test("화면 복사 → 레이아웃 확인", async () => { ... }); + test("메뉴 할당 → 조회 → 해제", async () => { ... }); +}); +``` + +--- + +## 📋 체크리스트 + +### 1단계: 기본 CRUD (6개 함수) +- [ ] `createScreen()` - 화면 생성 +- [ ] `getScreensByCompany()` - 화면 목록 (페이징) +- [ ] `getScreenByCode()` - 화면 코드로 조회 +- [ ] `getScreenById()` - 화면 ID로 조회 +- [ ] `updateScreen()` - 화면 업데이트 +- [ ] `deleteScreen()` - 화면 삭제 + +### 2단계: 레이아웃 관리 (3개 함수) +- [ ] `saveLayout()` - 레이아웃 저장 +- [ ] `getLayout()` - 레이아웃 조회 +- [ ] 레이아웃 삭제 로직 + +### 3단계: 템플릿 & 메뉴 (6개 함수) +- [ ] `getTemplates()` - 템플릿 목록 +- [ ] `createTemplate()` - 템플릿 생성 +- [ ] `assignToMenu()` - 메뉴 할당 +- [ ] `getMenuAssignments()` - 메뉴 할당 조회 +- [ ] `removeMenuAssignment()` - 메뉴 할당 해제 +- [ ] 테이블 레이블 조회 + +### 4단계: 복잡한 기능 (4개 함수) +- [ ] `copyScreen()` - 화면 복사 (트랜잭션) +- [ ] `applyTemplate()` - 템플릿 적용 (트랜잭션) +- [ ] `bulkDelete()` - 일괄 삭제 (트랜잭션) +- [ ] `reorderScreens()` - 순서 변경 (Raw Query) + +### 5단계: 테스트 & 검증 +- [ ] 단위 테스트 작성 (20개 이상) +- [ ] 통합 테스트 작성 (5개 이상) +- [ ] 성능 테스트 +- [ ] Prisma import 제거 확인 + +--- + +## 🎯 완료 기준 + +- ✅ 46개 Prisma 호출 모두 Raw Query로 전환 +- ✅ 모든 단위 테스트 통과 +- ✅ 모든 통합 테스트 통과 +- ✅ 성능 저하 없음 (기존 대비 ±10% 이내) +- ✅ 트랜잭션 정상 동작 확인 +- ✅ 에러 처리 및 롤백 정상 동작 + +--- + +**작성일**: 2025-09-30 +**예상 소요 시간**: 2-3일 +**담당자**: 백엔드 개발팀 +**우선순위**: 🔴 최우선 (Phase 2.1) \ No newline at end of file