ERP-node/제어관리_트랜잭션_및_조건부실행_개선방안.md

22 KiB

제어관리 시스템 트랜잭션 및 조건부 실행 개선방안

🚨 현재 문제점 분석

1. 트랜잭션 처리 부재

문제: 여러 액션 중 하나가 실패해도 이전 액션들이 그대로 유지됨

현재 상황:

저장액션1 (성공) → 저장액션2 (실패)
결과: 저장액션1의 데이터는 DB에 그대로 남아있음 (데이터 불일치)

예시 시나리오:

  1. 고객정보 저장 (성공)
  2. 주문정보 저장 (실패)
  3. 결제정보 저장 (실행되지 않음)

→ 고객정보만 저장되어 데이터 정합성 깨짐

2. 조건부 실행 로직 부재

문제: AND/OR 조건에 따른 유연한 액션 실행이 불가능

현재 한계:

  • 모든 액션이 순차적으로 실행됨
  • 하나 실패하면 전체 중단
  • 대안 액션 실행 불가

원하는 동작:

액션그룹1: (저장액션1 AND 저장액션2) OR 저장액션3
→ 저장액션1,2가 모두 성공하면 완료
→ 둘 중 하나라도 실패하면 저장액션3 실행

🎯 해결방안 설계

Phase 1: 트랜잭션 관리 시스템 구축

1.1 트랜잭션 단위 정의

// frontend/types/control-management.ts

export interface TransactionGroup {
  id: string;
  name: string;
  description?: string;
  actions: DataflowAction[];
  rollbackStrategy: RollbackStrategy;
  executionMode: "sequential" | "parallel";
  onFailure: FailureHandling;
}

export type RollbackStrategy =
  | "none" // 롤백 안함 (현재 방식)
  | "partial" // 실패한 액션만 롤백
  | "complete"; // 전체 트랜잭션 롤백

export type FailureHandling =
  | "stop" // 실패 시 중단 (현재 방식)
  | "continue" // 실패해도 계속 진행
  | "alternative"; // 대안 액션 실행

1.2 조건부 실행 로직 구조

export interface ConditionalExecutionPlan {
  id: string;
  name: string;
  conditions: ExecutionCondition[];
  logic: "AND" | "OR" | "CUSTOM";
  customLogic?: string; // "(A AND B) OR (C AND D)"
}

export interface ExecutionCondition {
  id: string;
  type: "action_group" | "validation" | "data_check";

  // 액션 그룹 조건
  actionGroup?: TransactionGroup;

  // 검증 조건
  validation?: {
    field: string;
    operator: ConditionOperator;
    value: unknown;
  };

  // 성공/실패 조건
  expectedResult: "success" | "failure" | "any";
}

1.3 액션 실행 결과 추적

export interface ActionExecutionResult {
  actionId: string;
  transactionId: string;
  status: "pending" | "running" | "success" | "failed" | "rolled_back";
  startTime: Date;
  endTime?: Date;
  result?: unknown;
  error?: {
    code: string;
    message: string;
    details?: unknown;
  };
  rollbackData?: unknown; // 롤백을 위한 데이터
}

export interface TransactionExecutionState {
  transactionId: string;
  status:
    | "pending"
    | "running"
    | "success"
    | "failed"
    | "rolling_back"
    | "rolled_back";
  actions: ActionExecutionResult[];
  rollbackActions?: ActionExecutionResult[];
  startTime: Date;
  endTime?: Date;
}

Phase 2: 고급 조건부 실행 시스템

2.1 조건부 액션 그룹 정의

export interface ConditionalActionGroup {
  id: string;
  name: string;
  description?: string;

  // 실행 조건
  executionCondition: {
    type: "always" | "conditional" | "fallback";
    conditions?: DataflowCondition[];
    logic?: "AND" | "OR";
  };

  // 액션들
  actions: DataflowAction[];

  // 성공/실패 조건 정의
  successCriteria: {
    type: "all_success" | "any_success" | "custom";
    customLogic?: string; // "action1 AND (action2 OR action3)"
  };

  // 다음 단계 정의
  onSuccess?: {
    nextGroup?: string;
    completeTransaction?: boolean;
  };

  onFailure?: {
    retryCount?: number;
    fallbackGroup?: string;
    rollbackStrategy?: RollbackStrategy;
  };
}

2.2 복잡한 실행 계획 예시

// 예시: 주문 처리 시스템
const orderProcessingPlan: ConditionalExecutionPlan = {
  id: "order_processing",
  name: "주문 처리",
  conditions: [
    {
      id: "primary_payment",
      type: "action_group",
      actionGroup: {
        id: "payment_group_1",
        name: "주결제 수단",
        actions: [
          {
            type: "database",
            operation: "UPDATE",
            tableName: "customer" /* ... */,
          },
          {
            type: "database",
            operation: "INSERT",
            tableName: "orders" /* ... */,
          },
          { type: "api", endpoint: "/payment/card" /* ... */ },
        ],
        rollbackStrategy: "complete",
        executionMode: "sequential",
      },
      expectedResult: "success",
    },
    {
      id: "alternative_payment",
      type: "action_group",
      actionGroup: {
        id: "payment_group_2",
        name: "대안 결제 수단",
        actions: [
          { type: "api", endpoint: "/payment/bank" /* ... */ },
          {
            type: "database",
            operation: "UPDATE",
            tableName: "orders" /* ... */,
          },
        ],
        rollbackStrategy: "complete",
        executionMode: "sequential",
      },
      expectedResult: "success",
    },
  ],
  logic: "OR", // primary_payment OR alternative_payment
  customLogic: "primary_payment OR alternative_payment",
};

Phase 3: 트랜잭션 실행 엔진 구현

3.1 트랜잭션 매니저 클래스

// frontend/lib/services/transactionManager.ts

export class TransactionManager {
  private activeTransactions: Map<string, TransactionExecutionState> =
    new Map();
  private rollbackHandlers: Map<string, RollbackHandler[]> = new Map();

  /**
   * 트랜잭션 실행
   */
  async executeTransaction(
    plan: ConditionalExecutionPlan,
    context: ExtendedControlContext
  ): Promise<TransactionExecutionResult> {
    const transactionId = this.generateTransactionId();
    const state: TransactionExecutionState = {
      transactionId,
      status: "pending",
      actions: [],
      startTime: new Date(),
    };

    this.activeTransactions.set(transactionId, state);

    try {
      state.status = "running";

      // 조건부 실행 로직 평가
      const executionResult = await this.evaluateExecutionPlan(
        plan,
        context,
        transactionId
      );

      if (executionResult.success) {
        state.status = "success";
      } else {
        state.status = "failed";

        // 실패 시 롤백 처리
        if (executionResult.requiresRollback) {
          await this.rollbackTransaction(transactionId);
        }
      }

      state.endTime = new Date();
      return executionResult;
    } catch (error) {
      state.status = "failed";
      state.endTime = new Date();

      await this.rollbackTransaction(transactionId);

      throw error;
    } finally {
      // 트랜잭션 정리 (일정 시간 후)
      setTimeout(() => this.cleanupTransaction(transactionId), 300000); // 5분 후
    }
  }

  /**
   * 실행 계획 평가
   */
  private async evaluateExecutionPlan(
    plan: ConditionalExecutionPlan,
    context: ExtendedControlContext,
    transactionId: string
  ): Promise<TransactionExecutionResult> {
    const results: Map<string, boolean> = new Map();

    // 각 조건별로 실행
    for (const condition of plan.conditions) {
      const result = await this.executeCondition(
        condition,
        context,
        transactionId
      );
      results.set(condition.id, result.success);

      // 실패 시 즉시 중단할지 결정
      if (!result.success && this.shouldStopOnFailure(plan, condition)) {
        return {
          success: false,
          message: `조건 ${condition.id} 실행 실패`,
          requiresRollback: true,
          results: Array.from(results.entries()),
        };
      }
    }

    // 전체 로직 평가
    const overallSuccess = this.evaluateLogic(
      plan.logic,
      plan.customLogic,
      results
    );

    return {
      success: overallSuccess,
      message: overallSuccess ? "모든 조건 실행 성공" : "조건 실행 실패",
      requiresRollback: !overallSuccess,
      results: Array.from(results.entries()),
    };
  }

  /**
   * 개별 조건 실행
   */
  private async executeCondition(
    condition: ExecutionCondition,
    context: ExtendedControlContext,
    transactionId: string
  ): Promise<{ success: boolean; result?: unknown }> {
    if (condition.type === "action_group" && condition.actionGroup) {
      return await this.executeActionGroup(
        condition.actionGroup,
        context,
        transactionId
      );
    }

    // 다른 조건 타입들 처리...
    return { success: true };
  }

  /**
   * 액션 그룹 실행
   */
  private async executeActionGroup(
    group: TransactionGroup,
    context: ExtendedControlContext,
    transactionId: string
  ): Promise<{ success: boolean; result?: unknown }> {
    const state = this.activeTransactions.get(transactionId)!;
    const groupResults: ActionExecutionResult[] = [];

    try {
      if (group.executionMode === "sequential") {
        // 순차 실행
        for (const action of group.actions) {
          const result = await this.executeAction(
            action,
            context,
            transactionId
          );
          groupResults.push(result);
          state.actions.push(result);

          if (result.status === "failed" && group.onFailure === "stop") {
            throw new Error(
              `액션 ${action.id} 실행 실패: ${result.error?.message}`
            );
          }
        }
      } else {
        // 병렬 실행
        const promises = group.actions.map((action) =>
          this.executeAction(action, context, transactionId)
        );
        const results = await Promise.allSettled(promises);

        results.forEach((result, index) => {
          const actionResult: ActionExecutionResult = {
            actionId: group.actions[index].id,
            transactionId,
            status:
              result.status === "fulfilled" && result.value.status === "success"
                ? "success"
                : "failed",
            startTime: new Date(),
            endTime: new Date(),
            result:
              result.status === "fulfilled" ? result.value.result : undefined,
            error:
              result.status === "rejected"
                ? { code: "EXECUTION_ERROR", message: result.reason }
                : undefined,
          };

          groupResults.push(actionResult);
          state.actions.push(actionResult);
        });
      }

      // 성공 기준 평가
      const success = this.evaluateSuccessCriteria(
        group.successCriteria,
        groupResults
      );

      if (!success && group.rollbackStrategy === "complete") {
        // 그룹 내 모든 액션 롤백
        await this.rollbackActionGroup(group, groupResults, transactionId);
      }

      return { success, result: groupResults };
    } catch (error) {
      // 오류 발생 시 롤백
      if (group.rollbackStrategy !== "none") {
        await this.rollbackActionGroup(group, groupResults, transactionId);
      }

      return { success: false, result: error };
    }
  }

  /**
   * 개별 액션 실행
   */
  private async executeAction(
    action: DataflowAction,
    context: ExtendedControlContext,
    transactionId: string
  ): Promise<ActionExecutionResult> {
    const result: ActionExecutionResult = {
      actionId: action.id,
      transactionId,
      status: "running",
      startTime: new Date(),
    };

    try {
      // 액션 타입별 실행
      let executionResult: unknown;

      switch (action.type) {
        case "database":
          executionResult = await this.executeDatabaseAction(action, context);

          // 롤백 데이터 저장 (UPDATE/DELETE의 경우)
          if (action.operation === "UPDATE" || action.operation === "DELETE") {
            result.rollbackData = await this.captureRollbackData(
              action,
              context
            );
          }
          break;

        case "api":
          executionResult = await this.executeApiAction(action, context);
          break;

        case "notification":
          executionResult = await this.executeNotificationAction(
            action,
            context
          );
          break;

        default:
          throw new Error(`Unsupported action type: ${action.type}`);
      }

      result.status = "success";
      result.result = executionResult;
      result.endTime = new Date();

      return result;
    } catch (error) {
      result.status = "failed";
      result.error = {
        code: "ACTION_EXECUTION_ERROR",
        message: error.message,
        details: error,
      };
      result.endTime = new Date();

      return result;
    }
  }

  /**
   * 트랜잭션 롤백
   */
  private async rollbackTransaction(transactionId: string): Promise<void> {
    const state = this.activeTransactions.get(transactionId);
    if (!state) return;

    state.status = "rolling_back";

    // 성공한 액션들을 역순으로 롤백
    const successfulActions = state.actions
      .filter((action) => action.status === "success")
      .reverse();

    const rollbackResults: ActionExecutionResult[] = [];

    for (const action of successfulActions) {
      try {
        const rollbackResult = await this.rollbackAction(action);
        rollbackResults.push(rollbackResult);
      } catch (error) {
        console.error(`롤백 실패: ${action.actionId}`, error);
        // 롤백 실패는 로그만 남기고 계속 진행
      }
    }

    state.rollbackActions = rollbackResults;
    state.status = "rolled_back";
    state.endTime = new Date();
  }

  /**
   * 개별 액션 롤백
   */
  private async rollbackAction(
    action: ActionExecutionResult
  ): Promise<ActionExecutionResult> {
    // 롤백 액션 실행
    // 이 부분은 액션 타입별로 구체적인 롤백 로직 구현 필요

    return {
      actionId: `rollback_${action.actionId}`,
      transactionId: action.transactionId,
      status: "success",
      startTime: new Date(),
      endTime: new Date(),
      result: "롤백 완료",
    };
  }

  /**
   * 로직 평가 (AND/OR/CUSTOM)
   */
  private evaluateLogic(
    logic: "AND" | "OR" | "CUSTOM",
    customLogic: string | undefined,
    results: Map<string, boolean>
  ): boolean {
    switch (logic) {
      case "AND":
        return Array.from(results.values()).every((result) => result);

      case "OR":
        return Array.from(results.values()).some((result) => result);

      case "CUSTOM":
        if (!customLogic) return false;
        return this.evaluateCustomLogic(customLogic, results);

      default:
        return false;
    }
  }

  /**
   * 커스텀 로직 평가
   */
  private evaluateCustomLogic(
    logic: string,
    results: Map<string, boolean>
  ): boolean {
    // "(A AND B) OR (C AND D)" 형태의 로직 파싱 및 평가
    let expression = logic;

    // 변수를 실제 결과값으로 치환
    for (const [id, result] of results) {
      expression = expression.replace(
        new RegExp(`\\b${id}\\b`, "g"),
        result.toString()
      );
    }

    // AND/OR를 JavaScript 연산자로 변환
    expression = expression
      .replace(/\bAND\b/g, "&&")
      .replace(/\bOR\b/g, "||")
      .replace(/\btrue\b/g, "true")
      .replace(/\bfalse\b/g, "false");

    try {
      // 안전한 평가를 위해 Function 생성자 사용
      return new Function(`return ${expression}`)();
    } catch (error) {
      console.error("커스텀 로직 평가 오류:", error);
      return false;
    }
  }

  // ... 기타 헬퍼 메서드들
}

export interface TransactionExecutionResult {
  success: boolean;
  message: string;
  requiresRollback: boolean;
  results: [string, boolean][];
}

export interface RollbackHandler {
  actionId: string;
  rollbackFn: () => Promise<void>;
}

3.2 데이터베이스 액션 실행기

// frontend/lib/services/databaseActionExecutor.ts

export class DatabaseActionExecutor {
  /**
   * 데이터베이스 액션 실행
   */
  static async executeAction(
    action: DataflowAction,
    context: ExtendedControlContext
  ): Promise<unknown> {
    const { tableName, operation, fields, conditions } = action;

    switch (operation) {
      case "INSERT":
        return await this.executeInsert(tableName!, fields!, context);

      case "UPDATE":
        return await this.executeUpdate(
          tableName!,
          fields!,
          conditions!,
          context
        );

      case "DELETE":
        return await this.executeDelete(tableName!, conditions!, context);

      case "SELECT":
        return await this.executeSelect(
          tableName!,
          fields!,
          conditions!,
          context
        );

      default:
        throw new Error(`Unsupported database operation: ${operation}`);
    }
  }

  /**
   * 롤백 데이터 캡처
   */
  static async captureRollbackData(
    action: DataflowAction,
    context: ExtendedControlContext
  ): Promise<unknown> {
    const { tableName, conditions } = action;

    if (action.operation === "UPDATE" || action.operation === "DELETE") {
      // 변경 전 데이터를 조회하여 저장
      return await this.executeSelect(tableName!, ["*"], conditions!, context);
    }

    return null;
  }

  /**
   * 롤백 실행
   */
  static async executeRollback(
    originalAction: ActionExecutionResult,
    rollbackData: unknown
  ): Promise<void> {
    // 원본 액션의 반대 작업 수행
    // INSERT -> DELETE
    // UPDATE -> UPDATE (원본 데이터로)
    // DELETE -> INSERT (원본 데이터로)
    // 구체적인 롤백 로직 구현...
  }

  // ... 개별 operation 구현 메서드들
}

Phase 4: 사용자 인터페이스 개선

4.1 조건부 실행 설정 UI

// frontend/components/screen/config-panels/ConditionalExecutionPanel.tsx

export const ConditionalExecutionPanel: React.FC<{
  config: ButtonDataflowConfig;
  onConfigChange: (config: ButtonDataflowConfig) => void;
}> = ({ config, onConfigChange }) => {
  return (
    <div className="space-y-6">
      {/* 실행 모드 선택 */}
      <div>
        <Label>실행 모드</Label>
        <Select>
          <SelectItem value="simple">단순 실행</SelectItem>
          <SelectItem value="conditional">조건부 실행</SelectItem>
          <SelectItem value="transaction">트랜잭션 실행</SelectItem>
        </Select>
      </div>

      {/* 트랜잭션 설정 */}
      <div>
        <Label>트랜잭션 롤백 전략</Label>
        <Select>
          <SelectItem value="none">롤백 안함</SelectItem>
          <SelectItem value="partial">부분 롤백</SelectItem>
          <SelectItem value="complete">전체 롤백</SelectItem>
        </Select>
      </div>

      {/* 액션 그룹 설정 */}
      <div>
        <Label>액션 그룹</Label>
        <ActionGroupEditor />
      </div>

      {/* 조건부 로직 설정 */}
      <div>
        <Label>실행 조건</Label>
        <ConditionalLogicEditor />
      </div>
    </div>
  );
};

4.2 트랜잭션 모니터링 UI

// frontend/components/screen/TransactionMonitor.tsx

export const TransactionMonitor: React.FC = () => {
  const [transactions, setTransactions] = useState<TransactionExecutionState[]>(
    []
  );

  return (
    <div className="space-y-4">
      <h3>트랜잭션 실행 현황</h3>

      {transactions.map((transaction) => (
        <Card key={transaction.transactionId}>
          <CardHeader>
            <div className="flex justify-between">
              <span>트랜잭션 {transaction.transactionId}</span>
              <Badge variant={getStatusVariant(transaction.status)}>
                {transaction.status}
              </Badge>
            </div>
          </CardHeader>

          <CardContent>
            <div className="space-y-2">
              {transaction.actions.map((action) => (
                <div key={action.actionId} className="flex justify-between">
                  <span>{action.actionId}</span>
                  <Badge variant={getStatusVariant(action.status)}>
                    {action.status}
                  </Badge>
                </div>
              ))}
            </div>

            {transaction.status === "failed" && (
              <Button
                onClick={() => retryTransaction(transaction.transactionId)}
              >
                재시도
              </Button>
            )}
          </CardContent>
        </Card>
      ))}
    </div>
  );
};

📋 구현 우선순위

🔥 즉시 구현 (Critical)

  1. TransactionManager 기본 구조 - 트랜잭션 단위 실행
  2. 롤백 메커니즘 - 실패 시 이전 상태 복구
  3. AND/OR 조건부 실행 - 기본적인 조건부 로직

단기 구현 (High)

  1. 데이터베이스 액션 실행기 - 실제 DB 작업 처리
  2. 에러 핸들링 강화 - 상세한 오류 정보 제공
  3. 트랜잭션 상태 추적 - 실행 과정 모니터링

📅 중장기 구현 (Medium)

  1. 복잡한 조건부 로직 - 커스텀 로직 지원
  2. 병렬 실행 지원 - 성능 최적화
  3. 모니터링 UI - 실시간 트랜잭션 추적

💡 기대 효과

데이터 일관성 보장

  • 트랜잭션 롤백으로 부분 실행 방지
  • All-or-Nothing 원칙 적용
  • 데이터 정합성 확보

유연한 비즈니스 로직

  • 복잡한 조건부 실행 지원
  • 대안 액션 자동 실행
  • 비즈니스 요구사항 정확한 반영

시스템 안정성 향상

  • 실패 시 자동 복구
  • 상세한 실행 로그
  • 문제 상황 신속 파악

이 개선방안에 대한 의견이나 우선순위 조정이 필요한 부분이 있으시면 말씀해 주세요!