docs: Add Phase 2.1 ScreenManagementService migration plan
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 <noreply@anthropic.com>
This commit is contained in:
parent
824e5f4827
commit
ba10e7a12b
|
|
@ -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<Array<{ table_name: string }>>`SELECT tablename ...`
|
||||
|
||||
// Line 876: 테이블 존재 확인
|
||||
await prisma.$queryRaw<Array<{ table_name: string }>>`SELECT tablename ...`
|
||||
|
||||
// Line 922: 테이블 컬럼 정보 조회
|
||||
await prisma.$queryRaw<Array<ColumnInfo>>`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<ScreenDefinition>(
|
||||
`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<ScreenDefinition>(
|
||||
`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)
|
||||
Loading…
Reference in New Issue