12 KiB
12 KiB
🖥️ Phase 2.1: ScreenManagementService Raw Query 전환 계획
📋 개요
ScreenManagementService는 46개의 Prisma 호출이 있는 가장 복잡한 서비스입니다. 화면 정의, 레이아웃, 메뉴 할당, 템플릿 등 다양한 기능을 포함합니다.
📊 기본 정보
| 항목 | 내용 |
|---|---|
| 파일 위치 | backend-node/src/services/screenManagementService.ts |
| 파일 크기 | 1,700+ 라인 |
| Prisma 호출 | 46개 |
| 복잡도 | 매우 높음 |
| 우선순위 | 🔴 최우선 |
🔍 Prisma 사용 현황 분석
1. 화면 정의 관리 (Screen Definitions) - 18개
// 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개
// 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개
// Line 1303: 템플릿 목록 조회
await prisma.screen_templates.findMany({ where })
// Line 1317: 템플릿 생성
await prisma.screen_templates.create({ data })
4. 메뉴 할당 (Screen Menu Assignments) - 5개
// 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개
// Line 117: 테이블 레이블 조회 (페이징)
await prisma.table_labels.findMany({ where, skip, take })
// Line 713: 테이블 레이블 조회 (전체)
await prisma.table_labels.findMany({ where })
6. 컬럼 레이블 (Column Labels) - 2개
// Line 948: 웹타입 정보 조회
await prisma.column_labels.findMany({ where, select })
// Line 1456: 컬럼 레이블 UPSERT
await prisma.column_labels.upsert({ where, create, update })
7. Raw Query 사용 (이미 있음) - 6개
// 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개
// 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단계: 단순 CRUD 전환 (findFirst, findMany, create, update, delete)
- 2단계: 복잡한 조회 전환 (include, join)
- 3단계: 트랜잭션 전환
- 4단계: Raw Query 개선
전략 2: 함수별 전환 우선순위
🔴 최우선 (기본 CRUD)
createScreen()- Line 70getScreensByCompany()- Line 99-105getScreenByCode()- Line 178getScreenById()- Line 205updateScreen()- Line 236deleteScreen()- Line 672
🟡 2순위 (레이아웃)
saveLayout()- Line 1096-1152getLayout()- Line 1193deleteLayout()- Line 1096
🟢 3순위 (템플릿 & 메뉴)
getTemplates()- Line 1303createTemplate()- Line 1317assignToMenu()- Line 1358getMenuAssignments()- Line 1376removeMenuAssignment()- Line 1401
🔵 4순위 (복잡한 기능)
copyScreen()- Line 593 (트랜잭션)applyTemplate()- Line 521 (트랜잭션)bulkDelete()- Line 788 (트랜잭션)reorderScreens()- Line 627 (Raw Query)
📝 전환 예시
예시 1: createScreen() 전환
기존 Prisma 코드:
// 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 코드:
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 코드:
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 코드:
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 코드:
await prisma.$transaction(async (tx) => {
const newScreen = await tx.screen_definitions.create({ data: { ... } });
await tx.screen_layouts.createMany({ data: layouts });
});
새로운 Raw Query 코드:
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 (...)`,
[...]
);
}
});
🧪 테스트 계획
단위 테스트
describe("ScreenManagementService Raw Query 전환 테스트", () => {
describe("createScreen", () => {
test("화면 생성 성공", async () => { ... });
test("중복 화면 코드 에러", async () => { ... });
});
describe("getScreensByCompany", () => {
test("페이징 조회 성공", async () => { ... });
test("회사별 필터링", async () => { ... });
});
describe("copyScreen", () => {
test("화면 복사 성공 (트랜잭션)", async () => { ... });
test("레이아웃 함께 복사", async () => { ... });
});
});
통합 테스트
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)