# 노드 시스템 - 버튼 통합 호환성 분석 **작성일**: 2025-01-02 **버전**: 1.0 **상태**: 🔍 분석 완료 --- ## 📋 목차 1. [개요](#개요) 2. [현재 시스템 분석](#현재-시스템-분석) 3. [호환성 분석](#호환성-분석) 4. [통합 전략](#통합-전략) 5. [마이그레이션 계획](#마이그레이션-계획) --- ## 개요 ### 목적 화면관리의 버튼 컴포넌트에 할당된 기존 제어 시스템을 새로운 노드 기반 제어 시스템으로 전환하기 위한 호환성 분석 ### 비교 대상 - **현재**: `relationshipId` 기반 제어 시스템 - **신규**: `flowId` 기반 노드 제어 시스템 --- ## 현재 시스템 분석 ### 1. 데이터 구조 #### ButtonDataflowConfig ```typescript interface ButtonDataflowConfig { controlMode: "relationship" | "none"; relationshipConfig?: { relationshipId: string; // 🔑 핵심: 관계 ID relationshipName: string; executionTiming: "before" | "after" | "replace"; contextData?: Record; }; controlDataSource?: "form" | "table-selection" | "both"; executionOptions?: ExecutionOptions; } ``` #### 관계 데이터 구조 ```typescript { relationshipId: "rel-123", conditions: [ { field: "status", operator: "equals", value: "active" } ], actionGroups: [ { name: "메인 액션", actions: [ { type: "database", operation: "INSERT", tableName: "users", fields: [...] } ] } ] } ``` --- ### 2. 실행 흐름 ``` ┌─────────────────────────────────────┐ │ 1. 버튼 클릭 │ │ OptimizedButtonComponent.tsx │ └─────────────┬───────────────────────┘ ↓ ┌─────────────────────────────────────┐ │ 2. executeButtonAction() │ │ ImprovedButtonActionExecutor.ts │ │ - executionPlan 생성 │ │ - before/after/replace 구분 │ └─────────────┬───────────────────────┘ ↓ ┌─────────────────────────────────────┐ │ 3. executeControls() │ │ - relationshipId로 관계 조회 │ │ - 조건 검증 │ └─────────────┬───────────────────────┘ ↓ ┌─────────────────────────────────────┐ │ 4. evaluateConditions() │ │ - formData 검증 │ │ - selectedRowsData 검증 │ └─────────────┬───────────────────────┘ ↓ ┌─────────────────────────────────────┐ │ 5. executeDataAction() │ │ - INSERT/UPDATE/DELETE 실행 │ │ - 순차적 액션 실행 │ └─────────────────────────────────────┘ ``` --- ### 3. 데이터 전달 방식 #### 입력 데이터 ```typescript { formData: { name: "김철수", email: "test@example.com", status: "active" }, selectedRowsData: [ { id: 1, name: "이영희" }, { id: 2, name: "박민수" } ], context: { buttonId: "btn-1", screenId: 123, companyCode: "COMPANY_A", userId: "user-1" } } ``` #### 액션 실행 시 ```typescript // 각 액션에 전체 데이터 전달 executeDataAction(action, { formData, selectedRowsData, context, }); ``` --- ## 새로운 노드 시스템 분석 ### 1. 데이터 구조 #### FlowData ```typescript interface FlowData { flowId: number; flowName: string; flowDescription: string; nodes: FlowNode[]; // 🔑 핵심: 노드 배열 edges: FlowEdge[]; // 🔑 핵심: 연결 정보 } ``` #### 노드 예시 ```typescript // 소스 노드 { id: "source-1", type: "tableSource", data: { tableName: "users", schema: "public", outputFields: [...] } } // 조건 노드 { id: "condition-1", type: "condition", data: { conditions: [{ field: "status", operator: "equals", value: "active" }], logic: "AND" } } // 액션 노드 { id: "insert-1", type: "insertAction", data: { targetTable: "users", fieldMappings: [...] } } ``` #### 연결 예시 ```typescript // 엣지 (노드 간 연결) { id: "edge-1", source: "source-1", target: "condition-1" }, { id: "edge-2", source: "condition-1", target: "insert-1", sourceHandle: "true" // TRUE 분기 } ``` --- ### 2. 실행 흐름 ``` ┌─────────────────────────────────────┐ │ 1. 버튼 클릭 │ │ FlowEditor 또는 Button Component │ └─────────────┬───────────────────────┘ ↓ ┌─────────────────────────────────────┐ │ 2. executeFlow() │ │ - flowId로 플로우 조회 │ │ - nodes + edges 로드 │ └─────────────┬───────────────────────┘ ↓ ┌─────────────────────────────────────┐ │ 3. topologicalSort() │ │ - 노드 의존성 분석 │ │ - 실행 순서 결정 │ │ Result: [["source"], ["insert", "update"]] │ └─────────────┬───────────────────────┘ ↓ ┌─────────────────────────────────────┐ │ 4. executeLevel() │ │ - 같은 레벨 노드 병렬 실행 │ │ - Promise.allSettled 사용 │ └─────────────┬───────────────────────┘ ↓ ┌─────────────────────────────────────┐ │ 5. executeNode() │ │ - 부모 노드 상태 확인 │ │ - 실패 시 스킵 │ └─────────────┬───────────────────────┘ ↓ ┌─────────────────────────────────────┐ │ 6. executeActionWithTransaction() │ │ - 독립 트랜잭션 시작 │ │ - 액션 실행 │ │ - 성공 시 커밋, 실패 시 롤백 │ └─────────────────────────────────────┘ ``` --- ### 3. 데이터 전달 방식 #### ExecutionContext ```typescript { sourceData: [ { id: 1, name: "김철수", status: "active" }, { id: 2, name: "이영희", status: "inactive" } ], nodeResults: Map { "source-1" => { status: "success", data: [...] }, "condition-1" => { status: "success", data: true }, "insert-1" => { status: "success", data: { insertedCount: 1 } } }, executionOrder: ["source-1", "condition-1", "insert-1"] } ``` #### 노드 실행 시 ```typescript // 부모 노드 결과 전달 const inputData = prepareInputData(node, parents, context); // 부모가 하나면 부모의 결과 데이터 // 부모가 여러 개면 모든 부모의 데이터 병합 ``` --- ## 호환성 분석 ### ✅ 호환 가능한 부분 #### 1. 조건 검증 **현재**: ```typescript { field: "status", operator: "equals", value: "active" } ``` **신규**: ```typescript { type: "condition", data: { conditions: [ { field: "status", operator: "equals", value: "active" } ] } } ``` **결론**: ✅ **조건 구조가 거의 동일** → 마이그레이션 쉬움 --- #### 2. 액션 실행 **현재**: ```typescript { type: "database", operation: "INSERT", tableName: "users", fields: [ { name: "name", value: "김철수" } ] } ``` **신규**: ```typescript { type: "insertAction", data: { targetTable: "users", fieldMappings: [ { sourceField: "name", targetField: "name" } ] } } ``` **결론**: ✅ **액션 개념이 동일** → 필드명만 변환하면 됨 --- #### 3. 데이터 소스 **현재**: ```typescript controlDataSource: "form" | "table-selection" | "both"; ``` **신규**: ```typescript { type: "tableSource", // 테이블 선택 데이터 // 또는 type: "manualInput", // 폼 데이터 } ``` **결론**: ✅ **소스 타입 매핑 가능** --- ### ⚠️ 차이점 및 주의사항 #### 1. 실행 타이밍 **현재**: ```typescript executionTiming: "before" | "after" | "replace"; ``` **신규**: ``` 노드 그래프 자체가 실행 순서를 정의 타이밍은 노드 연결로 표현됨 ``` **문제점**: - `before/after` 개념이 노드에 없음 - 버튼의 기본 액션과 제어를 어떻게 조합할지? **해결 방안**: ``` Option A: 버튼 액션을 노드로 표현 Button → [Before Nodes] → [Button Action Node] → [After Nodes] Option B: 실행 시점 지정 flowConfig: { flowId: 123, timing: "before" | "after" | "replace" } ``` --- #### 2. ActionGroups vs 병렬 실행 **현재**: ```typescript actionGroups: [ { name: "그룹1", actions: [action1, action2], // 순차 실행 }, ]; ``` **신규**: ``` 소스 ↓ ├─→ INSERT (병렬) ├─→ UPDATE (병렬) └─→ DELETE (병렬) ``` **문제점**: - 현재는 "그룹 내 순차, 그룹 간 조건부" - 신규는 "레벨별 병렬, 연쇄 중단" **해결 방안**: ``` 노드 연결로 순차/병렬 표현: 순차: INSERT → UPDATE → DELETE 병렬: Source → INSERT → UPDATE → DELETE ``` --- #### 3. 데이터 전달 방식 **현재**: ```typescript // 모든 액션에 동일한 데이터 전달 executeDataAction(action, { formData, selectedRowsData, context, }); ``` **신규**: ```typescript // 부모 노드 결과를 자식에게 전달 const inputData = parentResult.data || sourceData; ``` **문제점**: - 현재는 "원본 데이터 공유" - 신규는 "결과 데이터 체이닝" **해결 방안**: ```typescript // 버튼 실행 시 초기 데이터 설정 context.sourceData = { formData, selectedRowsData, }; // 각 노드는 필요에 따라 선택 - formData 사용 - 부모 결과 사용 - 둘 다 사용 ``` --- #### 4. 컨텍스트 정보 **현재**: ```typescript { buttonId: "btn-1", screenId: 123, companyCode: "COMPANY_A", userId: "user-1" } ``` **신규**: ```typescript // ExecutionContext에 추가 필요 { sourceData: [...], nodeResults: Map(), // 🆕 추가 필요 buttonContext?: { buttonId: string, screenId: number, companyCode: string, userId: string } } ``` **결론**: ✅ **컨텍스트 확장 가능** --- ## 통합 전략 ### 전략 1: 하이브리드 방식 (권장 ⭐⭐⭐) #### 개념 버튼 설정에서 `relationshipId` 대신 `flowId`를 저장하고, 기존 타이밍 개념 유지 #### 버튼 설정 ```typescript interface ButtonDataflowConfig { controlMode: "flow"; // 🆕 신규 모드 flowConfig?: { flowId: number; // 🔑 노드 플로우 ID flowName: string; executionTiming: "before" | "after" | "replace"; // 기존 유지 contextData?: Record; }; controlDataSource?: "form" | "table-selection" | "both"; } ``` #### 실행 로직 ```typescript async function executeButtonWithFlow( buttonConfig: ButtonDataflowConfig, formData: Record, context: ButtonExecutionContext ) { const { flowConfig } = buttonConfig; // 1. 플로우 조회 const flow = await getNodeFlow(flowConfig.flowId); // 2. 초기 데이터 준비 const executionContext: ExecutionContext = { sourceData: prepareSourceData(formData, context), nodeResults: new Map(), executionOrder: [], buttonContext: { // 🆕 버튼 컨텍스트 추가 buttonId: context.buttonId, screenId: context.screenId, companyCode: context.companyCode, userId: context.userId, }, }; // 3. 타이밍에 따라 실행 switch (flowConfig.executionTiming) { case "before": await executeFlow(flow, executionContext); await executeOriginalButtonAction(buttonConfig, context); break; case "after": await executeOriginalButtonAction(buttonConfig, context); await executeFlow(flow, executionContext); break; case "replace": await executeFlow(flow, executionContext); break; } } ``` #### 소스 데이터 준비 ```typescript function prepareSourceData( formData: Record, context: ButtonExecutionContext ): any[] { const { controlDataSource, selectedRowsData } = context; switch (controlDataSource) { case "form": return [formData]; // 폼 데이터를 배열로 case "table-selection": return selectedRowsData || []; // 테이블 선택 데이터 case "both": return [ { source: "form", data: formData }, { source: "table", data: selectedRowsData }, ]; default: return [formData]; } } ``` --- ### 전략 2: 완전 전환 방식 #### 개념 버튼 액션 자체를 노드로 표현 (버튼 = 플로우 트리거) #### 플로우 구조 ``` ManualInput (formData) ↓ Condition (status == "active") ↓ ┌─┴─┐ TRUE FALSE ↓ ↓ INSERT CANCEL ↓ ButtonAction (원래 버튼 액션) ``` #### 장점 - ✅ 시스템 단순화 (노드만 존재) - ✅ 시각적으로 명확 - ✅ 유연한 워크플로우 #### 단점 - ⚠️ 기존 버튼 개념 변경 - ⚠️ 마이그레이션 복잡 - ⚠️ UI 학습 곡선 --- ## 마이그레이션 계획 ### Phase 1: 하이브리드 지원 #### 목표 기존 `relationshipId` 방식과 새로운 `flowId` 방식 모두 지원 #### 작업 1. **ButtonDataflowConfig 확장** ```typescript interface ButtonDataflowConfig { controlMode: "relationship" | "flow" | "none"; // 기존 (하위 호환) relationshipConfig?: { relationshipId: string; executionTiming: "before" | "after" | "replace"; }; // 🆕 신규 flowConfig?: { flowId: number; executionTiming: "before" | "after" | "replace"; }; } ``` 2. **실행 로직 분기** ```typescript if (buttonConfig.controlMode === "flow") { await executeButtonWithFlow(buttonConfig, formData, context); } else if (buttonConfig.controlMode === "relationship") { await executeButtonWithRelationship(buttonConfig, formData, context); } ``` 3. **UI 업데이트** - 버튼 설정에 "제어 방식 선택" 추가 - "기존 관계" vs "노드 플로우" 선택 가능 --- ### Phase 2: 마이그레이션 도구 #### 관계 → 플로우 변환기 ```typescript async function migrateRelationshipToFlow( relationshipId: string ): Promise { // 1. 기존 관계 조회 const relationship = await getRelationship(relationshipId); // 2. 노드 생성 const nodes: FlowNode[] = []; const edges: FlowEdge[] = []; // 소스 노드 (formData 또는 table) const sourceNode = { id: "source-1", type: "manualInput", data: { fields: extractFields(relationship) }, }; nodes.push(sourceNode); // 조건 노드 if (relationship.conditions.length > 0) { const conditionNode = { id: "condition-1", type: "condition", data: { conditions: relationship.conditions, logic: relationship.logic || "AND", }, }; nodes.push(conditionNode); edges.push({ id: "e1", source: "source-1", target: "condition-1" }); } // 액션 노드들 let lastNodeId = relationship.conditions.length > 0 ? "condition-1" : "source-1"; relationship.actionGroups.forEach((group, groupIdx) => { group.actions.forEach((action, actionIdx) => { const actionNodeId = `action-${groupIdx}-${actionIdx}`; const actionNode = convertActionToNode(action, actionNodeId); nodes.push(actionNode); edges.push({ id: `e-${actionNodeId}`, source: lastNodeId, target: actionNodeId, }); // 순차 실행인 경우 if (group.sequential) { lastNodeId = actionNodeId; } }); }); // 3. 플로우 저장 const flowData = { flowName: `Migrated: ${relationship.name}`, flowDescription: `Migrated from relationship ${relationshipId}`, flowData: JSON.stringify({ nodes, edges }), }; const { flowId } = await createNodeFlow(flowData); // 4. 버튼 설정 업데이트 await updateButtonConfig(relationshipId, { controlMode: "flow", flowConfig: { flowId, executionTiming: relationship.timing || "before", }, }); return flowId; } ``` #### 액션 변환 로직 ```typescript function convertActionToNode(action: DataflowAction, nodeId: string): FlowNode { switch (action.operation) { case "INSERT": return { id: nodeId, type: "insertAction", data: { targetTable: action.tableName, fieldMappings: action.fields.map((f) => ({ sourceField: f.name, targetField: f.name, staticValue: f.type === "static" ? f.value : undefined, })), }, }; case "UPDATE": return { id: nodeId, type: "updateAction", data: { targetTable: action.tableName, whereConditions: action.conditions, fieldMappings: action.fields.map((f) => ({ sourceField: f.name, targetField: f.name, })), }, }; case "DELETE": return { id: nodeId, type: "deleteAction", data: { targetTable: action.tableName, whereConditions: action.conditions, }, }; default: throw new Error(`Unsupported operation: ${action.operation}`); } } ``` --- ### Phase 3: 완전 전환 #### 목표 모든 버튼이 노드 플로우 방식 사용 #### 작업 1. **마이그레이션 스크립트 실행** ```sql -- 모든 관계를 플로우로 변환 SELECT migrate_all_relationships_to_flows(); ``` 2. **UI에서 관계 모드 제거** ```typescript // controlMode에서 "relationship" 제거 type ControlMode = "flow" | "none"; ``` 3. **레거시 코드 정리** - `executeButtonWithRelationship()` 제거 - `RelationshipService` 제거 (또는 읽기 전용) --- ## 결론 ### ✅ 호환 가능 노드 시스템과 버튼 제어 시스템은 **충분히 호환 가능**합니다! ### 🎯 권장 방안 **하이브리드 방식 (전략 1)**으로 점진적 마이그레이션 #### 이유 1. ✅ **기존 시스템 유지** - 서비스 중단 없음 2. ✅ **점진적 전환** - 리스크 최소화 3. ✅ **유연성** - 두 방식 모두 활용 가능 4. ✅ **학습 곡선** - 사용자가 천천히 적응 ### 📋 다음 단계 1. **Phase 1 구현** (예상: 2일) - `ButtonDataflowConfig` 확장 - `executeButtonWithFlow()` 구현 - UI 선택 옵션 추가 2. **Phase 2 도구 개발** (예상: 1일) - 마이그레이션 스크립트 - 자동 변환 로직 3. **Phase 3 전환** (예상: 1일) - 데이터 마이그레이션 - 레거시 제거 ### 총 소요 시간 **약 4일** --- **참고 문서**: - [노드\_실행\_엔진\_설계.md](./노드_실행_엔진_설계.md) - [노드\_기반\_제어\_시스템\_개선\_계획.md](./노드_기반_제어_시스템_개선_계획.md)