# ๐Ÿš€ Prisma โ†’ Raw Query ์™„์ „ ์ „ํ™˜ ๊ณ„ํš์„œ ## ๐Ÿ“‹ ํ”„๋กœ์ ํŠธ ๊ฐœ์š” ### ๐ŸŽฏ ๋ชฉ์  ํ˜„์žฌ Node.js ๋ฐฑ์—”๋“œ์—์„œ Prisma ORM์„ ์™„์ „ํžˆ ์ œ๊ฑฐํ•˜๊ณ  Raw Query ๋ฐฉ์‹์œผ๋กœ ์ „ํ™˜ํ•˜์—ฌ **์™„์ „ ๋™์  ํ…Œ์ด๋ธ” ์ƒ์„ฑ ๋ฐ ๊ด€๋ฆฌ ์‹œ์Šคํ…œ**์„ ๊ตฌ์ถ•ํ•ฉ๋‹ˆ๋‹ค. ### ๐Ÿ” ํ˜„์žฌ ์ƒํ™ฉ ๋ถ„์„ - **์ด 52๊ฐœ ํŒŒ์ผ**์—์„œ Prisma ์‚ฌ์šฉ - **490๊ฐœ์˜ Prisma ํ˜ธ์ถœ** (ORM + Raw Query ํ˜ผ์žฌ) - **150๊ฐœ ์ด์ƒ์˜ ํ…Œ์ด๋ธ”** ์ •์˜ (schema.prisma) - **๋ณต์žกํ•œ ํŠธ๋žœ์žญ์…˜ ๋ฐ ๋™์  ์ฟผ๋ฆฌ** ๋‹ค์ˆ˜ ์กด์žฌ --- ## ๐Ÿ“Š Prisma ์‚ฌ์šฉ ํ˜„ํ™ฉ ๋ถ„์„ **์ด 42๊ฐœ ํŒŒ์ผ์—์„œ 444๊ฐœ์˜ Prisma ํ˜ธ์ถœ ๋ฐœ๊ฒฌ** โšก (Scripts ์ œ์™ธ) ### 1. **Prisma ์‚ฌ์šฉ ํŒŒ์ผ ๋ถ„๋ฅ˜** #### ๐Ÿ”ด **High Priority (ํ•ต์‹ฌ ์„œ๋น„์Šค) - 107๊ฐœ ํ˜ธ์ถœ** ``` backend-node/src/services/ โ”œโ”€โ”€ screenManagementService.ts # ํ™”๋ฉด ๊ด€๋ฆฌ (46๊ฐœ ํ˜ธ์ถœ) โญ ์ตœ์šฐ์„  โ”œโ”€โ”€ tableManagementService.ts # ํ…Œ์ด๋ธ” ๊ด€๋ฆฌ (35๊ฐœ ํ˜ธ์ถœ) โญ ์ตœ์šฐ์„  โ”œโ”€โ”€ dataflowService.ts # ๋ฐ์ดํ„ฐํ”Œ๋กœ์šฐ (0๊ฐœ ํ˜ธ์ถœ) โœ… ์ „ํ™˜ ์™„๋ฃŒ โ”œโ”€โ”€ dynamicFormService.ts # ๋™์  ํผ (15๊ฐœ ํ˜ธ์ถœ) โ”œโ”€โ”€ externalDbConnectionService.ts # ์™ธ๋ถ€DB (0๊ฐœ ํ˜ธ์ถœ) โœ… ์ „ํ™˜ ์™„๋ฃŒ โ”œโ”€โ”€ dataflowControlService.ts # ์ œ์–ด๊ด€๋ฆฌ (6๊ฐœ ํ˜ธ์ถœ) โ”œโ”€โ”€ ddlExecutionService.ts # DDL ์‹คํ–‰ (6๊ฐœ ํ˜ธ์ถœ) โ”œโ”€โ”€ authService.ts # ์ธ์ฆ (5๊ฐœ ํ˜ธ์ถœ) โ””โ”€โ”€ multiConnectionQueryService.ts # ๋‹ค์ค‘ ์—ฐ๊ฒฐ (4๊ฐœ ํ˜ธ์ถœ) ``` #### ๐ŸŸก **Medium Priority (๊ด€๋ฆฌ ๊ธฐ๋Šฅ) - 142๊ฐœ ํ˜ธ์ถœ** ``` 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๊ฐœ ํ˜ธ์ถœ) โญ ์‹ ๊ทœ ๋ฐœ๊ฒฌ โ”œโ”€โ”€ adminService.ts # ๊ด€๋ฆฌ์ž (3๊ฐœ ํ˜ธ์ถœ) โ””โ”€โ”€ referenceCacheService.ts # ์บ์‹œ (3๊ฐœ ํ˜ธ์ถœ) ``` #### ๐ŸŸข **Low Priority (์ปจํŠธ๋กค๋Ÿฌ & ๋ผ์šฐํŠธ) - 188๊ฐœ ํ˜ธ์ถœ** ``` 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๊ฐœ ํ˜ธ์ถœ) โญ ์‹ ๊ทœ ๋ฐœ๊ฒฌ backend-node/src/routes/ โ”œโ”€โ”€ ddlRoutes.ts # DDL ๋ผ์šฐํŠธ (2๊ฐœ ํ˜ธ์ถœ) โญ ์‹ ๊ทœ ๋ฐœ๊ฒฌ โ””โ”€โ”€ companyManagementRoutes.ts # ํšŒ์‚ฌ ๊ด€๋ฆฌ ๋ผ์šฐํŠธ (2๊ฐœ ํ˜ธ์ถœ) โญ ์‹ ๊ทœ ๋ฐœ๊ฒฌ backend-node/src/config/ โ””โ”€โ”€ database.ts # ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์„ค์ • (4๊ฐœ ํ˜ธ์ถœ) #### ๐Ÿ—‘๏ธ **์‚ญ์ œ ์˜ˆ์ • Scripts - 60๊ฐœ ํ˜ธ์ถœ** โš ๏ธ ์‚ฌ์šฉํ•˜์ง€ ์•Š์Œ ``` 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๊ฐœ ํ˜ธ์ถœ) ๐Ÿ—‘๏ธ ์‚ญ์ œ backend-node/ (๋ฃจํŠธ) โ””โ”€โ”€ clean-screen-tables.js # ํ™”๋ฉด ํ…Œ์ด๋ธ” ์ •๋ฆฌ (7๊ฐœ ํ˜ธ์ถœ) ๐Ÿ—‘๏ธ ์‚ญ์ œ ```` **โš ๏ธ ์‚ญ์ œ ๊ณ„ํš**: ์ด ์Šคํฌ๋ฆฝํŠธ๋“ค์€ ๊ฐœ๋ฐœ/๋ฐฐํฌ ๋„๊ตฌ๋กœ ์šด์˜ ์‹œ์Šคํ…œ์—์„œ ์‚ฌ์šฉํ•˜์ง€ ์•Š์œผ๋ฏ€๋กœ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ์ „์— ์‚ญ์ œ ์˜ˆ์ • ### 2. **๋ณต์žก๋„๋ณ„ ๋ถ„๋ฅ˜** #### ๐Ÿ”ฅ **๋งค์šฐ ๋ณต์žก (ํŠธ๋žœ์žญ์…˜ + ๋™์  ์ฟผ๋ฆฌ) - ์ตœ์šฐ์„  ์ฒ˜๋ฆฌ** - `screenManagementService.ts` (46๊ฐœ) - ํ™”๋ฉด ์ •์˜ ๊ด€๋ฆฌ, JSON ์ฒ˜๋ฆฌ - `tableManagementService.ts` (35๊ฐœ) - ํ…Œ์ด๋ธ” ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ ๊ด€๋ฆฌ, DDL ์‹คํ–‰ - `dataflowService.ts` (0๊ฐœ) - โœ… **์ „ํ™˜ ์™„๋ฃŒ** (Phase 2.3) - `dynamicFormService.ts` (15๊ฐœ) - UPSERT ๋ฐ ๋™์  ํ…Œ์ด๋ธ” ์ฒ˜๋ฆฌ - `externalDbConnectionService.ts` (0๊ฐœ) - โœ… **์ „ํ™˜ ์™„๋ฃŒ** (Phase 2.5) - `dataflowControlService.ts` (6๊ฐœ) - ๋ณต์žกํ•œ ์ œ์–ด ๋กœ์ง - `enhancedDataflowControlService.ts` (0๊ฐœ) - ๋‹ค์ค‘ ์—ฐ๊ฒฐ ์ œ์–ด (Raw Query๋งŒ ์‚ฌ์šฉ) - `multiConnectionQueryService.ts` (4๊ฐœ) - ์™ธ๋ถ€ DB ์—ฐ๊ฒฐ #### ๐ŸŸ  **๋ณต์žก (Raw Query ํ˜ผ์žฌ) - 2์ˆœ์œ„** - `multilangService.ts` (25๊ฐœ) - ์žฌ๊ท€ ์ฟผ๋ฆฌ, ๋‹ค๊ตญ์–ด ์ฒ˜๋ฆฌ - `batchService.ts` (16๊ฐœ) - ๋ฐฐ์น˜ ์ž‘์—… ๊ด€๋ฆฌ - `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๊ฐœ ํ˜ธ์ถœ๋งŒํผ ์ค„์ผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. --- ## ๐Ÿ—๏ธ 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 { const client = await this.pool.connect(); try { const result = await client.query(text, params); return result.rows; } finally { client.release(); } } // ํŠธ๋žœ์žญ์…˜ ์‹คํ–‰ static async transaction( callback: (client: PoolClient) => Promise ): Promise { 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(); } } ```` ### 2. **๋™์  ์ฟผ๋ฆฌ ๋นŒ๋”** ```typescript // utils/queryBuilder.ts export class QueryBuilder { // SELECT ์ฟผ๋ฆฌ ๋นŒ๋” static select( tableName: string, options: { columns?: string[]; where?: Record; 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, 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, where: Record ) { 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) { 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 { 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): boolean { return Object.keys(where).every((key) => this.validateColumnName(key)); } } ``` --- ## ๐Ÿ“… ๋‹จ๊ณ„๋ณ„ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ๊ณ„ํš ### **Phase 1: ๊ธฐ๋ฐ˜ ๊ตฌ์กฐ ๊ตฌ์ถ• (1์ฃผ)** #### 1.1 ์ƒˆ๋กœ์šด ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์•„ํ‚คํ…์ฒ˜ ๊ตฌํ˜„ - [ ] `DatabaseManager` ํด๋ž˜์Šค ๊ตฌํ˜„ - [ ] `QueryBuilder` ์œ ํ‹ธ๋ฆฌํ‹ฐ ๊ตฌํ˜„ - [ ] ํƒ€์ž… ์ •์˜ ๋ฐ ๊ฒ€์ฆ ๋กœ์ง ๊ตฌํ˜„ - [ ] ์—ฐ๊ฒฐ ํ’€ ๋ฐ ํŠธ๋žœ์žญ์…˜ ๊ด€๋ฆฌ #### 1.2 ํ…Œ์ŠคํŠธ ํ™˜๊ฒฝ ๊ตฌ์ถ• - [ ] ๋‹จ์œ„ ํ…Œ์ŠคํŠธ ์ž‘์„ฑ - [ ] ํ†ตํ•ฉ ํ…Œ์ŠคํŠธ ํ™˜๊ฒฝ ๊ตฌ์„ฑ - [ ] ์„ฑ๋Šฅ ๋ฒค์น˜๋งˆํฌ ๋„๊ตฌ ์ค€๋น„ ### **Phase 2: ํ•ต์‹ฌ ์„œ๋น„์Šค ์ „ํ™˜ (3์ฃผ) - ์ตœ์šฐ์„ ** #### 2.1 ํ™”๋ฉด ๊ด€๋ฆฌ ์„œ๋น„์Šค ์ „ํ™˜ (์šฐ์„ ์ˆœ์œ„ 1) - 46๊ฐœ ํ˜ธ์ถœ ```typescript // ๊ธฐ์กด Prisma ์ฝ”๋“œ (๋ณต์žกํ•œ JSON ์ฒ˜๋ฆฌ) const screenData = await prisma.screen_definitions.findMany({ where: { company_code: companyCode, screen_config: { path: ["type"], equals: "form" }, }, include: { screen_components: true }, }); // ์ƒˆ๋กœ์šด 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", }, ], }); const screenData = await DatabaseManager.query(query, params); ``` #### 2.2 ํ…Œ์ด๋ธ” ๊ด€๋ฆฌ ์„œ๋น„์Šค ์ „ํ™˜ (์šฐ์„ ์ˆœ์œ„ 2) - 35๊ฐœ ํ˜ธ์ถœ - [ ] ๋™์  ํ…Œ์ด๋ธ” ์ƒ์„ฑ/์‚ญ์ œ ๋กœ์ง ์ „ํ™˜ - [ ] ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ ๊ด€๋ฆฌ ์‹œ์Šคํ…œ ๊ฐœ์„  - [ ] DDL ์‹คํ–‰ ํŠธ๋žœ์žญ์…˜ ์ฒ˜๋ฆฌ - [ ] ์ปฌ๋Ÿผ ํƒ€์ž… ๋ณ€ํ™˜ ๋กœ์ง ์ตœ์ ํ™” #### 2.3 ๋ฐ์ดํ„ฐํ”Œ๋กœ์šฐ ์„œ๋น„์Šค ์ „ํ™˜ (์šฐ์„ ์ˆœ์œ„ 3) - 31๊ฐœ ํ˜ธ์ถœ โญ ์‹ ๊ทœ ๋ฐœ๊ฒฌ - [ ] ๋ณต์žกํ•œ ๊ด€๊ณ„ ๊ด€๋ฆฌ ๋กœ์ง ์ „ํ™˜ - [ ] ํŠธ๋žœ์žญ์…˜ ๊ธฐ๋ฐ˜ ๋ฐ์ดํ„ฐ ์ด๋™ ์ฒ˜๋ฆฌ - [ ] JSON ๊ธฐ๋ฐ˜ ์„ค์ • ๊ด€๋ฆฌ ๊ฐœ์„  - [ ] ๋‹ค์ค‘ ํ…Œ์ด๋ธ” ์กฐ์ธ ์ตœ์ ํ™” #### 2.4 ๋™์  ํผ ์„œ๋น„์Šค ์ „ํ™˜ (์šฐ์„ ์ˆœ์œ„ 4) - 15๊ฐœ ํ˜ธ์ถœ - [ ] UPSERT ๋กœ์ง Raw Query๋กœ ์ „ํ™˜ - [ ] ๋™์  ํ…Œ์ด๋ธ” ์ฒ˜๋ฆฌ ๋กœ์ง ๊ฐœ์„  - [ ] ํŠธ๋žœ์žญ์…˜ ์ฒ˜๋ฆฌ ์ตœ์ ํ™” #### 2.5 ์™ธ๋ถ€ DB ์—ฐ๊ฒฐ ์„œ๋น„์Šค ์ „ํ™˜ (์šฐ์„ ์ˆœ์œ„ 5) - 15๊ฐœ ํ˜ธ์ถœ - [ ] ๋‹ค์ค‘ DB ์—ฐ๊ฒฐ ๊ด€๋ฆฌ ๋กœ์ง - [ ] ์—ฐ๊ฒฐ ํ’€ ๊ด€๋ฆฌ ์‹œ์Šคํ…œ - [ ] ์™ธ๋ถ€ DB ์Šคํ‚ค๋งˆ ๋™๊ธฐํ™” ### **Phase 3: ๊ด€๋ฆฌ ๊ธฐ๋Šฅ ์ „ํ™˜ (2.5์ฃผ)** #### 3.1 ๋‹ค๊ตญ์–ด ์„œ๋น„์Šค ์ „ํ™˜ - 25๊ฐœ ํ˜ธ์ถœ - [ ] ์žฌ๊ท€ ์ฟผ๋ฆฌ (WITH RECURSIVE) ์ „ํ™˜ - [ ] ๋ฒˆ์—ญ ๋ฐ์ดํ„ฐ ๊ด€๋ฆฌ ์ตœ์ ํ™” - [ ] ๋‹ค๊ตญ์–ด ์บ์‹œ ์‹œ์Šคํ…œ ๊ตฌํ˜„ #### 3.2 ๋ฐฐ์น˜ ๊ด€๋ จ ์„œ๋น„์Šค ์ „ํ™˜ - 40๊ฐœ ํ˜ธ์ถœ โญ ๋Œ€๊ทœ๋ชจ ์‹ ๊ทœ ๋ฐœ๊ฒฌ - [ ] `batchService.ts` (16๊ฐœ) - ๋ฐฐ์น˜ ์ž‘์—… ๊ด€๋ฆฌ - [ ] `batchExternalDbService.ts` (8๊ฐœ) - ๋ฐฐ์น˜ ์™ธ๋ถ€DB - [ ] `batchExecutionLogService.ts` (7๊ฐœ) - ๋ฐฐ์น˜ ์‹คํ–‰ ๋กœ๊ทธ - [ ] `batchManagementService.ts` (5๊ฐœ) - ๋ฐฐ์น˜ ๊ด€๋ฆฌ - [ ] `batchSchedulerService.ts` (4๊ฐœ) - ๋ฐฐ์น˜ ์Šค์ผ€์ค„๋Ÿฌ #### 3.3 ํ‘œ์ค€ ๊ด€๋ฆฌ ์„œ๋น„์Šค ์ „ํ™˜ - 41๊ฐœ ํ˜ธ์ถœ - [ ] `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๊ฐœ ํ˜ธ์ถœ โญ ๋Œ€๊ทœ๋ชจ ์‹ ๊ทœ ๋ฐœ๊ฒฌ - [ ] `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 ๋ผ์šฐํŠธ #### 4.3 ์„ค์ • ๋ฐ ๊ธฐ๋ฐ˜ ๊ตฌ์กฐ - 6๊ฐœ ํ˜ธ์ถœ - [ ] `database.ts` (4๊ฐœ) - ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์„ค์ • - [ ] `companyManagementRoutes.ts` (2๊ฐœ) - ํšŒ์‚ฌ ๊ด€๋ฆฌ ๋ผ์šฐํŠธ ### **Phase 5: ์‚ฌ์šฉํ•˜์ง€ ์•Š๋Š” Scripts ์‚ญ์ œ (0.5์ฃผ) ๐Ÿ—‘๏ธ** #### 5.1 ๋ถˆํ•„์š”ํ•œ ์Šคํฌ๋ฆฝํŠธ ํŒŒ์ผ ์‚ญ์ œ - 60๊ฐœ ํ˜ธ์ถœ ์ œ๊ฑฐ - [ ] `backend-node/scripts/` ์ „์ฒด ํด๋” ์‚ญ์ œ (53๊ฐœ ํ˜ธ์ถœ) - [ ] `backend-node/clean-screen-tables.js` ์‚ญ์ œ (7๊ฐœ ํ˜ธ์ถœ) - [ ] ๊ด€๋ จ package.json ์Šคํฌ๋ฆฝํŠธ ์ •๋ฆฌ - [ ] ๋ฌธ์„œ์—์„œ ์Šคํฌ๋ฆฝํŠธ ์ฐธ์กฐ ์ œ๊ฑฐ **โœ… ํšจ๊ณผ**: 60๊ฐœ Prisma ํ˜ธ์ถœ์„ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ์—†์ด ์ œ๊ฑฐํ•˜์—ฌ ์ž‘์—…๋Ÿ‰ ๋Œ€ํญ ๊ฐ์†Œ ### **Phase 6: Prisma ์™„์ „ ์ œ๊ฑฐ (0.5์ฃผ)** #### 6.1 Prisma ์˜์กด์„ฑ ์ œ๊ฑฐ - [ ] `package.json`์—์„œ Prisma ์ œ๊ฑฐ - [ ] `schema.prisma` ํŒŒ์ผ ์‚ญ์ œ - [ ] ๊ด€๋ จ ์„ค์ • ํŒŒ์ผ ์ •๋ฆฌ #### 6.2 ์ตœ์ข… ๊ฒ€์ฆ ๋ฐ ์ตœ์ ํ™” - [ ] ์ „์ฒด ๊ธฐ๋Šฅ ํ…Œ์ŠคํŠธ - [ ] ์„ฑ๋Šฅ ์ตœ์ ํ™” - [ ] ๋ฌธ์„œํ™” ์—…๋ฐ์ดํŠธ --- ## ๐Ÿ”„ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ์ „๋žต ### 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( operations: ((client: PoolClient) => Promise)[] ): Promise { 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(); 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 }) => PrismaCompatibilityLayer.findUnique("user_info", options), findMany: (options?: any) => PrismaCompatibilityLayer.findMany("user_info", options), create: (options: { data: Record }) => 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 { 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 { 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) ### **Phase 1.5: ์ธ์ฆ ๋ฐ ๊ด€๋ฆฌ์ž ์„œ๋น„์Šค (์šฐ์„  ์ „ํ™˜) - 36๊ฐœ ํ˜ธ์ถœ** โœ… **์™„๋ฃŒ** > **์šฐ์„ ์ˆœ์œ„ ๋ณ€๊ฒฝ**: Phase 2 ์ง„ํ–‰ ์ „ ์ธ์ฆ/๊ด€๋ฆฌ ์‹œ์Šคํ…œ์„ ๋จผ์ € ์ „ํ™˜ํ•˜์—ฌ ์ „์ฒด ์‹œ์Šคํ…œ์˜ ์•ˆ์ •์ ์ธ ๊ธฐ๋ฐ˜ ๊ตฌ์ถ• - [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 - [x] **ํ…Œ์ŠคํŠธ** โœ… **์™„๋ฃŒ** - [x] ๋‹จ์œ„ ํ…Œ์ŠคํŠธ (30๊ฐœ ํ…Œ์ŠคํŠธ ๋ชจ๋‘ ํ†ต๊ณผ) - [x] ํ†ตํ•ฉ ํ…Œ์ŠคํŠธ ์ž‘์„ฑ ์™„๋ฃŒ ### **Phase 2: ํ•ต์‹ฌ ์„œ๋น„์Šค (3์ฃผ) - 107๊ฐœ ํ˜ธ์ถœ** #### โœ… ์™„๋ฃŒ๋œ ์„œ๋น„์Šค - [x] **ScreenManagementService ์ „ํ™˜ (46๊ฐœ)** โœ… **์™„๋ฃŒ** (Phase 2.1) - [x] 46๊ฐœ Prisma ํ˜ธ์ถœ ์ „ํ™˜ ์™„๋ฃŒ - [x] 18๊ฐœ ๋‹จ์œ„ ํ…Œ์ŠคํŠธ ํ†ต๊ณผ - [x] 6๊ฐœ ํ†ตํ•ฉ ํ…Œ์ŠคํŠธ ์ž‘์„ฑ ์™„๋ฃŒ - [x] ์‹ค์ œ ์šด์˜ ๋ฒ„๊ทธ ๋ฐœ๊ฒฌ ๋ฐ ์ˆ˜์ • (์†Œ์ˆ˜์  ์ขŒํ‘œ) - ๐Ÿ“„ **[PHASE2_SCREEN_MANAGEMENT_MIGRATION.md](PHASE2_SCREEN_MANAGEMENT_MIGRATION.md)** - [x] **TableManagementService ์ „ํ™˜ (33๊ฐœ)** โœ… **์™„๋ฃŒ** (Phase 2.2) - [x] 33๊ฐœ Prisma ํ˜ธ์ถœ ์ „ํ™˜ ์™„๋ฃŒ ($queryRaw 26๊ฐœ + ORM 7๊ฐœ) - [x] ๋‹จ์œ„ ํ…Œ์ŠคํŠธ ์ž‘์„ฑ ์™„๋ฃŒ - [x] Prisma import ์™„์ „ ์ œ๊ฑฐ - ๐Ÿ“„ **[PHASE2.2_TABLE_MANAGEMENT_MIGRATION.md](PHASE2.2_TABLE_MANAGEMENT_MIGRATION.md)** - [x] **DDLExecutionService ์ „ํ™˜ (6๊ฐœ)** โœ… **์™„๋ฃŒ** (Phase 2.3) - [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)** - [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 ๐ŸŸข ๋‚ฎ์€ ์šฐ์„ ์ˆœ์œ„ - 13๊ฐœ Prisma ํ˜ธ์ถœ ($queryRaw 11๊ฐœ + ORM 2๊ฐœ) - SQL์€ 85% ์ž‘์„ฑ ์™„๋ฃŒ โ†’ `query()` ํ•จ์ˆ˜๋กœ ๊ต์ฒด๋งŒ ํ•„์š” - ๐Ÿ“„ **[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 ์™„์ „ ์ œ๊ฑฐ - ๐Ÿ“„ **[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)** #### โœ… ๋‹ค๋ฅธ Phase๋กœ ์ด๋™ - [x] ~~AuthService ์ „ํ™˜ (5๊ฐœ)~~ โ†’ Phase 1.5๋กœ ์ด๋™ - [x] ~~MultiConnectionQueryService ์ „ํ™˜ (4๊ฐœ)~~ โ†’ Phase 1 ์™„๋ฃŒ ### **Phase 3: ๊ด€๋ฆฌ ๊ธฐ๋Šฅ (2.5์ฃผ) - 162๊ฐœ ํ˜ธ์ถœ** - [ ] MultiLangService ์ „ํ™˜ (25๊ฐœ) - ์žฌ๊ท€ ์ฟผ๋ฆฌ - [ ] ๋ฐฐ์น˜ ๊ด€๋ จ ์„œ๋น„์Šค ์ „ํ™˜ (40๊ฐœ) โญ ๋Œ€๊ทœ๋ชจ ์‹ ๊ทœ ๋ฐœ๊ฒฌ - [ ] BatchService (16๊ฐœ), 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๊ฐœ) - [ ] ๊ธฐ๋Šฅ๋ณ„ ํ…Œ์ŠคํŠธ ์™„๋ฃŒ ### **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๊ฐœ) - [ ] ์ „์ฒด ๊ธฐ๋Šฅ ํ…Œ์ŠคํŠธ ### **Phase 5: Scripts ์‚ญ์ œ (0.5์ฃผ) - 60๊ฐœ ํ˜ธ์ถœ ์ œ๊ฑฐ ๐Ÿ—‘๏ธ** - [ ] ๋ถˆํ•„์š”ํ•œ ์Šคํฌ๋ฆฝํŠธ ํŒŒ์ผ ์‚ญ์ œ (60๊ฐœ) ๐Ÿ—‘๏ธ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ๋ถˆํ•„์š” - [ ] backend-node/scripts/ ์ „์ฒด ํด๋” ์‚ญ์ œ (53๊ฐœ) - [ ] backend-node/clean-screen-tables.js ์‚ญ์ œ (7๊ฐœ) - [ ] package.json ์Šคํฌ๋ฆฝํŠธ ์ •๋ฆฌ - [ ] ๋ฌธ์„œ์—์„œ ์Šคํฌ๋ฆฝํŠธ ์ฐธ์กฐ ์ œ๊ฑฐ ### **Phase 6: ์™„์ „ ์ œ๊ฑฐ (0.5์ฃผ)** - [ ] 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๋ฅผ ์™„์ „ํžˆ ์ œ๊ฑฐํ•˜๊ณ  ์ง„์ •ํ•œ ๋™์  ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์‹œ์Šคํ…œ์„ ๊ตฌ์ถ•ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค! ๐Ÿš€ **โšก ์ค‘์š”**: ์ด์ œ ๋ชจ๋“  Prisma ์‚ฌ์šฉ ๋ถ€๋ถ„์ด ํŒŒ์•…๋˜์—ˆ์œผ๋ฏ€๋กœ, ๋ˆ„๋ฝ ์—†๋Š” ์™„์ „ํ•œ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜์ด ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค.