# 노드 시스템 - 버튼 통합 호환성 분석

**작성일**: 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<string, any>;
  };

  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<string, NodeResult> {
    "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<string, any>;
  };

  controlDataSource?: "form" | "table-selection" | "both";
}
```

#### 실행 로직

```typescript
async function executeButtonWithFlow(
  buttonConfig: ButtonDataflowConfig,
  formData: Record<string, any>,
  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<string, any>,
  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<number> {
  // 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)
