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

853 lines
22 KiB
Markdown
Raw Permalink Normal View History

# 제어관리 시스템 트랜잭션 및 조건부 실행 개선방안
## 🚨 현재 문제점 분석
### 1. 트랜잭션 처리 부재
**문제**: 여러 액션 중 하나가 실패해도 이전 액션들이 그대로 유지됨
#### 현재 상황:
```
저장액션1 (성공) → 저장액션2 (실패)
결과: 저장액션1의 데이터는 DB에 그대로 남아있음 (데이터 불일치)
```
#### 예시 시나리오:
1. **고객정보 저장** (성공)
2. **주문정보 저장** (실패)
3. **결제정보 저장** (실행되지 않음)
→ 고객정보만 저장되어 데이터 정합성 깨짐
### 2. 조건부 실행 로직 부재
**문제**: AND/OR 조건에 따른 유연한 액션 실행이 불가능
#### 현재 한계:
- 모든 액션이 순차적으로 실행됨
- 하나 실패하면 전체 중단
- 대안 액션 실행 불가
#### 원하는 동작:
```
액션그룹1: (저장액션1 AND 저장액션2) OR 저장액션3
→ 저장액션1,2가 모두 성공하면 완료
→ 둘 중 하나라도 실패하면 저장액션3 실행
```
## 🎯 해결방안 설계
## Phase 1: 트랜잭션 관리 시스템 구축
### 1.1 트랜잭션 단위 정의
```typescript
// 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 조건부 실행 로직 구조
```typescript
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 액션 실행 결과 추적
```typescript
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 조건부 액션 그룹 정의
```typescript
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 복잡한 실행 계획 예시
```typescript
// 예시: 주문 처리 시스템
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 트랜잭션 매니저 클래스
```typescript
// 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 데이터베이스 액션 실행기
```typescript
// 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
```typescript
// 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
```typescript
// 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)
4. **데이터베이스 액션 실행기** - 실제 DB 작업 처리
5. **에러 핸들링 강화** - 상세한 오류 정보 제공
6. **트랜잭션 상태 추적** - 실행 과정 모니터링
### 📅 중장기 구현 (Medium)
7. **복잡한 조건부 로직** - 커스텀 로직 지원
8. **병렬 실행 지원** - 성능 최적화
9. **모니터링 UI** - 실시간 트랜잭션 추적
## 💡 기대 효과
### 데이터 일관성 보장
- 트랜잭션 롤백으로 부분 실행 방지
- All-or-Nothing 원칙 적용
- 데이터 정합성 확보
### 유연한 비즈니스 로직
- 복잡한 조건부 실행 지원
- 대안 액션 자동 실행
- 비즈니스 요구사항 정확한 반영
### 시스템 안정성 향상
- 실패 시 자동 복구
- 상세한 실행 로그
- 문제 상황 신속 파악
이 개선방안에 대한 의견이나 우선순위 조정이 필요한 부분이 있으시면 말씀해 주세요!