저장버튼 제어기능 (insert)
This commit is contained in:
517
frontend/lib/services/optimizedButtonDataflowService.ts
Normal file
517
frontend/lib/services/optimizedButtonDataflowService.ts
Normal file
@@ -0,0 +1,517 @@
|
||||
/**
|
||||
* 🔥 성능 최적화: 버튼 데이터플로우 서비스
|
||||
*
|
||||
* 즉시 응답 + 백그라운드 실행 패턴으로
|
||||
* 사용자에게 최고의 성능을 제공합니다.
|
||||
*/
|
||||
|
||||
import {
|
||||
ButtonActionType,
|
||||
ButtonTypeConfig,
|
||||
ButtonDataflowConfig,
|
||||
DataflowExecutionResult,
|
||||
DataflowCondition,
|
||||
} from "@/types/screen";
|
||||
import { dataflowConfigCache } from "./dataflowCache";
|
||||
import { dataflowJobQueue, JobPriority } from "./dataflowJobQueue";
|
||||
import { apiClient } from "@/lib/api/client";
|
||||
|
||||
export interface OptimizedExecutionResult {
|
||||
jobId: string;
|
||||
immediateResult?: any;
|
||||
isBackground?: boolean;
|
||||
timing?: "before" | "after" | "replace";
|
||||
}
|
||||
|
||||
export interface QuickValidationResult {
|
||||
success: boolean;
|
||||
message?: string;
|
||||
canExecuteImmediately: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* 🔥 최적화된 버튼 데이터플로우 서비스
|
||||
*
|
||||
* 핵심 원칙:
|
||||
* 1. 즉시 응답 우선 (0-100ms)
|
||||
* 2. 복잡한 작업은 백그라운드
|
||||
* 3. 캐시 활용으로 속도 향상
|
||||
* 4. 스마트한 타이밍 제어
|
||||
*/
|
||||
export class OptimizedButtonDataflowService {
|
||||
/**
|
||||
* 🔥 메인 엔트리포인트: 즉시 응답 + 백그라운드 실행
|
||||
*/
|
||||
static async executeButtonWithDataflow(
|
||||
buttonId: string,
|
||||
actionType: ButtonActionType,
|
||||
buttonConfig: ButtonTypeConfig,
|
||||
contextData: Record<string, any>,
|
||||
companyCode: string,
|
||||
): Promise<OptimizedExecutionResult> {
|
||||
const { enableDataflowControl, dataflowTiming } = buttonConfig;
|
||||
|
||||
// 🔥 제어관리가 비활성화된 경우: 즉시 실행
|
||||
if (!enableDataflowControl) {
|
||||
const result = await this.executeOriginalAction(actionType, buttonConfig, contextData);
|
||||
return {
|
||||
jobId: "immediate",
|
||||
immediateResult: result,
|
||||
timing: undefined,
|
||||
};
|
||||
}
|
||||
|
||||
// 🔥 타이밍별 즉시 응답 전략
|
||||
switch (dataflowTiming) {
|
||||
case "before":
|
||||
return await this.executeBeforeTiming(buttonId, actionType, buttonConfig, contextData, companyCode);
|
||||
|
||||
case "after":
|
||||
return await this.executeAfterTiming(buttonId, actionType, buttonConfig, contextData, companyCode);
|
||||
|
||||
case "replace":
|
||||
return await this.executeReplaceTiming(buttonId, actionType, buttonConfig, contextData, companyCode);
|
||||
|
||||
default:
|
||||
// 기본값은 after
|
||||
return await this.executeAfterTiming(buttonId, actionType, buttonConfig, contextData, companyCode);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 🔥 After 타이밍: 즉시 기존 액션 + 백그라운드 제어관리
|
||||
*
|
||||
* 가장 일반적이고 안전한 패턴
|
||||
* - 기존 액션 즉시 실행 (50-200ms)
|
||||
* - 제어관리는 백그라운드에서 처리
|
||||
*/
|
||||
private static async executeAfterTiming(
|
||||
buttonId: string,
|
||||
actionType: ButtonActionType,
|
||||
buttonConfig: ButtonTypeConfig,
|
||||
contextData: Record<string, any>,
|
||||
companyCode: string,
|
||||
): Promise<OptimizedExecutionResult> {
|
||||
// 🔥 Step 1: 기존 액션 즉시 실행
|
||||
const immediateResult = await this.executeOriginalAction(actionType, buttonConfig, contextData);
|
||||
|
||||
// 🔥 Step 2: 제어관리는 백그라운드에서 실행
|
||||
const enrichedContext = {
|
||||
...contextData,
|
||||
originalActionResult: immediateResult,
|
||||
};
|
||||
|
||||
const jobId = dataflowJobQueue.enqueue(
|
||||
buttonId,
|
||||
actionType,
|
||||
buttonConfig,
|
||||
enrichedContext,
|
||||
companyCode,
|
||||
"normal", // 일반 우선순위
|
||||
);
|
||||
|
||||
return {
|
||||
jobId,
|
||||
immediateResult,
|
||||
isBackground: true,
|
||||
timing: "after",
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 🔥 Before 타이밍: 빠른 검증 + 기존 액션
|
||||
*
|
||||
* 검증 목적으로 주로 사용
|
||||
* - 간단한 검증: 즉시 처리
|
||||
* - 복잡한 검증: 백그라운드 처리
|
||||
*/
|
||||
private static async executeBeforeTiming(
|
||||
buttonId: string,
|
||||
actionType: ButtonActionType,
|
||||
buttonConfig: ButtonTypeConfig,
|
||||
contextData: Record<string, any>,
|
||||
companyCode: string,
|
||||
): Promise<OptimizedExecutionResult> {
|
||||
// 🔥 설정 캐시에서 빠르게 로드
|
||||
const dataflowConfig = buttonConfig.dataflowConfig || (await dataflowConfigCache.getConfig(buttonId));
|
||||
|
||||
if (!dataflowConfig) {
|
||||
// 설정이 없으면 기존 액션만 실행
|
||||
const result = await this.executeOriginalAction(actionType, buttonConfig, contextData);
|
||||
return { jobId: "immediate", immediateResult: result, timing: "before" };
|
||||
}
|
||||
|
||||
// 간단한 검증인지 판단
|
||||
const isSimpleValidation = await this.isSimpleValidationOnly(dataflowConfig);
|
||||
|
||||
if (isSimpleValidation) {
|
||||
// 🔥 간단한 검증: 메모리에서 즉시 처리 (1-10ms)
|
||||
const validationResult = await this.executeQuickValidation(dataflowConfig, contextData);
|
||||
|
||||
if (!validationResult.success) {
|
||||
return {
|
||||
jobId: "validation_failed",
|
||||
immediateResult: {
|
||||
success: false,
|
||||
message: validationResult.message,
|
||||
},
|
||||
timing: "before",
|
||||
};
|
||||
}
|
||||
|
||||
// 검증 통과 시 기존 액션 실행
|
||||
const actionResult = await this.executeOriginalAction(actionType, buttonConfig, contextData);
|
||||
|
||||
return {
|
||||
jobId: "immediate",
|
||||
immediateResult: actionResult,
|
||||
timing: "before",
|
||||
};
|
||||
} else {
|
||||
// 🔥 복잡한 검증: 사용자에게 알림 후 백그라운드 처리
|
||||
const jobId = dataflowJobQueue.enqueue(
|
||||
buttonId,
|
||||
actionType,
|
||||
buttonConfig,
|
||||
contextData,
|
||||
companyCode,
|
||||
"high", // 높은 우선순위 (사용자 대기 중)
|
||||
);
|
||||
|
||||
return {
|
||||
jobId,
|
||||
immediateResult: {
|
||||
success: true,
|
||||
message: "검증 중입니다. 잠시만 기다려주세요.",
|
||||
processing: true,
|
||||
},
|
||||
isBackground: true,
|
||||
timing: "before",
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 🔥 Replace 타이밍: 제어관리로 완전 대체
|
||||
*
|
||||
* 기존 액션 대신 제어관리만 실행
|
||||
* - 간단한 제어: 즉시 실행
|
||||
* - 복잡한 제어: 백그라운드 실행
|
||||
*/
|
||||
private static async executeReplaceTiming(
|
||||
buttonId: string,
|
||||
actionType: ButtonActionType,
|
||||
buttonConfig: ButtonTypeConfig,
|
||||
contextData: Record<string, any>,
|
||||
companyCode: string,
|
||||
): Promise<OptimizedExecutionResult> {
|
||||
const dataflowConfig = buttonConfig.dataflowConfig || (await dataflowConfigCache.getConfig(buttonId));
|
||||
|
||||
if (!dataflowConfig) {
|
||||
throw new Error("Replace 모드이지만 제어관리 설정이 없습니다.");
|
||||
}
|
||||
|
||||
// 간단한 제어관리인지 판단
|
||||
const isSimpleControl = this.isSimpleControl(dataflowConfig);
|
||||
|
||||
if (isSimpleControl) {
|
||||
// 🔥 간단한 제어: 즉시 실행
|
||||
try {
|
||||
const result = await this.executeSimpleDataflow(dataflowConfig, contextData, companyCode);
|
||||
|
||||
return {
|
||||
jobId: "immediate",
|
||||
immediateResult: result,
|
||||
timing: "replace",
|
||||
};
|
||||
} catch (error) {
|
||||
return {
|
||||
jobId: "immediate",
|
||||
immediateResult: {
|
||||
success: false,
|
||||
message: "제어관리 실행 중 오류가 발생했습니다.",
|
||||
error: error.message,
|
||||
},
|
||||
timing: "replace",
|
||||
};
|
||||
}
|
||||
} else {
|
||||
// 🔥 복잡한 제어: 백그라운드 실행
|
||||
const jobId = dataflowJobQueue.enqueue(buttonId, actionType, buttonConfig, contextData, companyCode, "normal");
|
||||
|
||||
return {
|
||||
jobId,
|
||||
immediateResult: {
|
||||
success: true,
|
||||
message: "사용자 정의 작업을 처리 중입니다...",
|
||||
processing: true,
|
||||
},
|
||||
isBackground: true,
|
||||
timing: "replace",
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 🔥 간단한 조건인지 판단
|
||||
*
|
||||
* 메모리에서 즉시 처리 가능한 조건:
|
||||
* - 조건 5개 이하
|
||||
* - 단순 비교 연산자만 사용
|
||||
* - 그룹핑 없음
|
||||
*/
|
||||
private static async isSimpleValidationOnly(config: ButtonDataflowConfig): Promise<boolean> {
|
||||
if (config.controlMode !== "advanced") {
|
||||
return true; // 간편 모드는 일단 간단하다고 가정
|
||||
}
|
||||
|
||||
const conditions = config.directControl?.conditions || [];
|
||||
|
||||
return (
|
||||
conditions.length <= 5 &&
|
||||
conditions.every((c) => c.type === "condition" && ["=", "!=", ">", "<", ">=", "<="].includes(c.operator || ""))
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 🔥 간단한 제어관리인지 판단
|
||||
*/
|
||||
private static isSimpleControl(config: ButtonDataflowConfig): boolean {
|
||||
if (config.controlMode === "simple") {
|
||||
return true; // 간편 모드는 대부분 간단
|
||||
}
|
||||
|
||||
const actions = config.directControl?.actions || [];
|
||||
const conditions = config.directControl?.conditions || [];
|
||||
|
||||
// 액션 3개 이하, 조건 5개 이하면 간단한 제어로 판단
|
||||
return actions.length <= 3 && conditions.length <= 5;
|
||||
}
|
||||
|
||||
/**
|
||||
* 🔥 빠른 검증 (메모리에서 즉시 처리)
|
||||
*/
|
||||
private static async executeQuickValidation(
|
||||
config: ButtonDataflowConfig,
|
||||
data: Record<string, any>,
|
||||
): Promise<QuickValidationResult> {
|
||||
if (config.controlMode === "simple") {
|
||||
// 간편 모드는 일단 통과 (실제 검증은 백그라운드에서)
|
||||
return {
|
||||
success: true,
|
||||
canExecuteImmediately: true,
|
||||
};
|
||||
}
|
||||
|
||||
const conditions = config.directControl?.conditions || [];
|
||||
|
||||
for (const condition of conditions) {
|
||||
if (condition.type === "condition") {
|
||||
const fieldValue = data[condition.field!];
|
||||
const isValid = this.evaluateSimpleCondition(fieldValue, condition.operator!, condition.value);
|
||||
|
||||
if (!isValid) {
|
||||
return {
|
||||
success: false,
|
||||
message: `조건 불만족: ${condition.field} ${condition.operator} ${condition.value}`,
|
||||
canExecuteImmediately: true,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
canExecuteImmediately: true,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 🔥 단순 조건 평가 (메모리에서 즉시)
|
||||
*/
|
||||
private static evaluateSimpleCondition(fieldValue: any, operator: string, conditionValue: any): boolean {
|
||||
switch (operator) {
|
||||
case "=":
|
||||
return fieldValue === conditionValue;
|
||||
case "!=":
|
||||
return fieldValue !== conditionValue;
|
||||
case ">":
|
||||
return Number(fieldValue) > Number(conditionValue);
|
||||
case "<":
|
||||
return Number(fieldValue) < Number(conditionValue);
|
||||
case ">=":
|
||||
return Number(fieldValue) >= Number(conditionValue);
|
||||
case "<=":
|
||||
return Number(fieldValue) <= Number(conditionValue);
|
||||
case "LIKE":
|
||||
return String(fieldValue).toLowerCase().includes(String(conditionValue).toLowerCase());
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 🔥 간단한 데이터플로우 즉시 실행
|
||||
*/
|
||||
private static async executeSimpleDataflow(
|
||||
config: ButtonDataflowConfig,
|
||||
contextData: Record<string, any>,
|
||||
companyCode: string,
|
||||
): Promise<DataflowExecutionResult> {
|
||||
try {
|
||||
const response = await apiClient.post("/api/button-dataflow/execute-simple", {
|
||||
config,
|
||||
contextData,
|
||||
companyCode,
|
||||
});
|
||||
|
||||
if (response.data.success) {
|
||||
return response.data.data as DataflowExecutionResult;
|
||||
} else {
|
||||
throw new Error(response.data.message || "Simple dataflow execution failed");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Simple dataflow execution failed:", error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 🔥 기존 액션 실행 (최적화)
|
||||
*/
|
||||
private static async executeOriginalAction(
|
||||
actionType: ButtonActionType,
|
||||
buttonConfig: ButtonTypeConfig,
|
||||
contextData: Record<string, any>,
|
||||
): Promise<any> {
|
||||
const startTime = performance.now();
|
||||
|
||||
try {
|
||||
// 액션별 분기 처리
|
||||
switch (actionType) {
|
||||
case "save":
|
||||
return await this.executeSaveAction(buttonConfig, contextData);
|
||||
case "delete":
|
||||
return await this.executeDeleteAction(buttonConfig, contextData);
|
||||
case "search":
|
||||
return await this.executeSearchAction(buttonConfig, contextData);
|
||||
case "edit":
|
||||
return await this.executeEditAction(buttonConfig, contextData);
|
||||
case "add":
|
||||
return await this.executeAddAction(buttonConfig, contextData);
|
||||
case "reset":
|
||||
return await this.executeResetAction(buttonConfig, contextData);
|
||||
case "submit":
|
||||
return await this.executeSubmitAction(buttonConfig, contextData);
|
||||
case "close":
|
||||
return await this.executeCloseAction(buttonConfig, contextData);
|
||||
case "popup":
|
||||
return await this.executePopupAction(buttonConfig, contextData);
|
||||
case "navigate":
|
||||
return await this.executeNavigateAction(buttonConfig, contextData);
|
||||
default:
|
||||
return {
|
||||
success: true,
|
||||
message: `${actionType} 액션이 실행되었습니다.`,
|
||||
};
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`Action execution failed: ${actionType}`, error);
|
||||
return {
|
||||
success: false,
|
||||
message: `${actionType} 액션 실행 중 오류가 발생했습니다.`,
|
||||
error: error.message,
|
||||
};
|
||||
} finally {
|
||||
const executionTime = performance.now() - startTime;
|
||||
if (executionTime > 200) {
|
||||
console.warn(`🐌 Slow action: ${actionType} took ${executionTime.toFixed(2)}ms`);
|
||||
} else {
|
||||
console.log(`⚡ ${actionType} completed in ${executionTime.toFixed(2)}ms`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 개별 액션 구현들
|
||||
*/
|
||||
private static async executeSaveAction(config: ButtonTypeConfig, data: Record<string, any>) {
|
||||
// TODO: 실제 저장 로직 구현
|
||||
return { success: true, message: "저장되었습니다." };
|
||||
}
|
||||
|
||||
private static async executeDeleteAction(config: ButtonTypeConfig, data: Record<string, any>) {
|
||||
// TODO: 실제 삭제 로직 구현
|
||||
return { success: true, message: "삭제되었습니다." };
|
||||
}
|
||||
|
||||
private static async executeSearchAction(config: ButtonTypeConfig, data: Record<string, any>) {
|
||||
// TODO: 실제 검색 로직 구현
|
||||
return { success: true, message: "검색되었습니다.", data: [] };
|
||||
}
|
||||
|
||||
private static async executeEditAction(config: ButtonTypeConfig, data: Record<string, any>) {
|
||||
return { success: true, message: "수정 모드로 전환되었습니다." };
|
||||
}
|
||||
|
||||
private static async executeAddAction(config: ButtonTypeConfig, data: Record<string, any>) {
|
||||
return { success: true, message: "추가 모드로 전환되었습니다." };
|
||||
}
|
||||
|
||||
private static async executeResetAction(config: ButtonTypeConfig, data: Record<string, any>) {
|
||||
return { success: true, message: "초기화되었습니다." };
|
||||
}
|
||||
|
||||
private static async executeSubmitAction(config: ButtonTypeConfig, data: Record<string, any>) {
|
||||
return { success: true, message: "제출되었습니다." };
|
||||
}
|
||||
|
||||
private static async executeCloseAction(config: ButtonTypeConfig, data: Record<string, any>) {
|
||||
return { success: true, message: "닫기 액션이 실행되었습니다." };
|
||||
}
|
||||
|
||||
private static async executePopupAction(config: ButtonTypeConfig, data: Record<string, any>) {
|
||||
return {
|
||||
success: true,
|
||||
message: "팝업이 열렸습니다.",
|
||||
popupUrl: config.navigateUrl,
|
||||
popupScreenId: config.popupScreenId,
|
||||
};
|
||||
}
|
||||
|
||||
private static async executeNavigateAction(config: ButtonTypeConfig, data: Record<string, any>) {
|
||||
return {
|
||||
success: true,
|
||||
message: "페이지 이동이 실행되었습니다.",
|
||||
navigateUrl: config.navigateUrl,
|
||||
navigateTarget: config.navigateTarget,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 🔥 작업 상태 조회
|
||||
*/
|
||||
static getJobStatus(jobId: string): { status: string; result?: any; progress?: number } {
|
||||
try {
|
||||
return dataflowJobQueue.getJobStatus(jobId);
|
||||
} catch (error) {
|
||||
return { status: "not_found" };
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 🔥 성능 메트릭 조회
|
||||
*/
|
||||
static getPerformanceMetrics(): {
|
||||
cache: any;
|
||||
queue: any;
|
||||
} {
|
||||
return {
|
||||
cache: dataflowConfigCache.getMetrics(),
|
||||
queue: dataflowJobQueue.getMetrics(),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// 🔥 전역 접근을 위한 싱글톤 서비스
|
||||
export const optimizedButtonDataflowService = OptimizedButtonDataflowService;
|
||||
Reference in New Issue
Block a user