# ๐Ÿšจ ๋ฒ„ํŠผ ์ œ์–ด๊ด€๋ฆฌ ๊ธฐ๋Šฅ ํ†ตํ•ฉ - ์ž ์žฌ์  ๋ฌธ์ œ์  ๋ฐ ํ•ด๊ฒฐ๋ฐฉ์•ˆ ## ๐Ÿ“Š ์„ฑ๋Šฅ ๊ด€๋ จ ๋ฌธ์ œ์  ### 1. **๋ฒ„ํŠผ ํด๋ฆญ ์‹œ ์ง€์—ฐ ์‹œ๊ฐ„ ์ฆ๊ฐ€** **๋ฌธ์ œ์ :** - ๊ธฐ์กด ๋ฒ„ํŠผ์€ ๋‹จ์ˆœํ•œ ์•ก์…˜๋งŒ ์ˆ˜ํ–‰ (50-100ms) - ์ œ์–ด๊ด€๋ฆฌ ์ถ”๊ฐ€ ์‹œ ๋ณตํ•ฉ์ ์ธ ์ฒ˜๋ฆฌ๋กœ ์ธํ•œ ์ง€์—ฐ ๊ฐ€๋Šฅ์„ฑ - ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์กฐ๊ฑด ๊ฒ€์ฆ: 100-300ms - ๋ณต์žกํ•œ ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง ์‹คํ–‰: 500ms-2์ดˆ - ๋‹ค์ค‘ ํ…Œ์ด๋ธ” ์—…๋ฐ์ดํŠธ: 1-5์ดˆ **ํ˜„์žฌ ์ฝ”๋“œ์—์„œ ํ™•์ธ๋œ ๋ฌธ์ œ:** ```typescript // InteractiveScreenViewer.tsx์—์„œ ๋ฒ„ํŠผ ํด๋ฆญ ์ฒ˜๋ฆฌ๊ฐ€ ๋™๊ธฐ์  const handleButtonClick = async () => { // ๊ธฐ์กด์—๋Š” ๋‹จ์ˆœํ•œ ์•ก์…˜๋งŒ switch (actionType) { case "save": await handleSaveAction(); break; // ~100ms // ์ œ์–ด๊ด€๋ฆฌ ์ถ”๊ฐ€ ์‹œ ๋ณตํ•ฉ ์ฒ˜๋ฆฌ๋กœ ์ฆ๊ฐ€ ์˜ˆ์ƒ } }; ``` **ํ•ด๊ฒฐ๋ฐฉ์•ˆ:** 1. **๋น„๋™๊ธฐ ์ฒ˜๋ฆฌ + ๋กœ๋”ฉ ์ƒํƒœ** ```typescript const [isExecuting, setIsExecuting] = useState(false); const handleButtonClick = async () => { setIsExecuting(true); try { // ์ œ์–ด๊ด€๋ฆฌ ์‹คํ–‰ } finally { setIsExecuting(false); } }; ``` 2. **๋ฐฑ๊ทธ๋ผ์šด๋“œ ์‹คํ–‰ ์˜ต์…˜** ```typescript // ๊ธด๊ธ‰ํ•˜์ง€ ์•Š์€ ์ œ์–ด๊ด€๋ฆฌ๋Š” ๋ฐฑ๊ทธ๋ผ์šด๋“œ์—์„œ ์‹คํ–‰ if (config.executionOptions?.asyncExecution) { // ์ฆ‰์‹œ ์„ฑ๊ณต ์‘๋‹ต // ๋ฐฑ๊ทธ๋ผ์šด๋“œ์—์„œ ์ œ์–ด๊ด€๋ฆฌ ์‹คํ–‰ } ``` ### 2. **๋ฉ”๋ชจ๋ฆฌ ์‚ฌ์šฉ๋Ÿ‰ ์ฆ๊ฐ€** **๋ฌธ์ œ์ :** - ๊ฐ ๋ฒ„ํŠผ๋งˆ๋‹ค ์ œ์–ด๊ด€๋ฆฌ ์„ค์ •์„ ๋ฉ”๋ชจ๋ฆฌ์— ๋ณด๊ด€ - ๋ณต์žกํ•œ ์กฐ๊ฑด/์•ก์…˜ ์„ค์ •์œผ๋กœ ์ธํ•œ ๋ฉ”๋ชจ๋ฆฌ ์‚ฌ์šฉ๋Ÿ‰ ์ฆ๊ฐ€ - ๋Œ€๋Ÿ‰์˜ ๋ฒ„ํŠผ์ด ์žˆ๋Š” ํ™”๋ฉด์—์„œ ๋ฉ”๋ชจ๋ฆฌ ๋ถ€์กฑ ๊ฐ€๋Šฅ์„ฑ **ํ•ด๊ฒฐ๋ฐฉ์•ˆ:** 1. **์ง€์—ฐ ๋กœ๋”ฉ** ```typescript // ์ œ์–ด๊ด€๋ฆฌ ์„ค์ •์„ ํ•„์š”ํ•  ๋•Œ๋งŒ ๋กœ๋“œ const loadDataflowConfig = useCallback(async () => { if (config.enableDataflowControl && !dataflowConfig) { const config = await apiClient.get(`/button-dataflow/config/${buttonId}`); setDataflowConfig(config.data); } }, [buttonId, config.enableDataflowControl]); ``` 2. **์„ค์ • ์บ์‹ฑ** ```typescript // LRU ์บ์‹œ๋กœ ์ž์ฃผ ์‚ฌ์šฉ๋˜๋Š” ์„ค์ •๋งŒ ๋ฉ”๋ชจ๋ฆฌ ๋ณด๊ด€ const configCache = new LRUCache({ max: 100, ttl: 300000 }); // 5๋ถ„ TTL ``` ### 3. **๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์„ฑ๋Šฅ ์˜ํ–ฅ** **๋ฌธ์ œ์ :** - ๋ฒ„ํŠผ ํด๋ฆญ๋งˆ๋‹ค ๋ณต์žกํ•œ SQL ์ฟผ๋ฆฌ ์‹คํ–‰ - EventTriggerService์˜ ํ˜„์žฌ ๊ตฌ์กฐ์ƒ ์ „์ฒด ๊ด€๊ณ„๋„ ์Šค์บ” ```typescript // eventTriggerService.ts - ๋ชจ๋“  ๊ด€๊ณ„๋„๋ฅผ ๊ฒ€์ƒ‰ํ•˜๋Š” ๋น„ํšจ์œจ์ ์ธ ์ฟผ๋ฆฌ const diagrams = await prisma.$queryRaw` SELECT * FROM dataflow_diagrams WHERE company_code = ${companyCode} AND (category::text = '"data-save"' OR ...) `; ``` **ํ•ด๊ฒฐ๋ฐฉ์•ˆ:** 1. **์ธ๋ฑ์Šค ์ตœ์ ํ™”** ```sql -- ๋ณตํ•ฉ ์ธ๋ฑ์Šค ์ถ”๊ฐ€ CREATE INDEX idx_dataflow_button_lookup ON dataflow_diagrams USING GIN ((control->'buttonId')) WHERE category @> '["button-trigger"]'; ``` 2. **์บ์‹ฑ ๊ณ„์ธต ์ถ”๊ฐ€** ```typescript // ๋ฒ„ํŠผ๋ณ„ ์ œ์–ด๊ด€๋ฆฌ ๋งคํ•‘์„ ์บ์‹œ const buttonDataflowCache = new Map(); ``` ## ๐Ÿ”ง ํ™•์žฅ์„ฑ ๊ด€๋ จ ๋ฌธ์ œ์  ### 4. **์„ค์ • ๋ณต์žก๋„ ์ฆ๊ฐ€** **๋ฌธ์ œ์ :** - ๊ธฐ์กด ๋‹จ์ˆœํ•œ ๋ฒ„ํŠผ ์„ค์ •์—์„œ ๋ณต์žกํ•œ ์ œ์–ด๊ด€๋ฆฌ ์„ค์ • ์ถ”๊ฐ€ - ์‚ฌ์šฉ์ž ํ˜ผ๋ž€ ๊ฐ€๋Šฅ์„ฑ - UI๊ฐ€ ๋„ˆ๋ฌด ๋ณต์žกํ•ด์งˆ ์œ„ํ—˜ **ํ˜„์žฌ UI ๊ตฌ์กฐ ๋ฌธ์ œ:** ```typescript // ButtonConfigPanel.tsx๊ฐ€ ์ด๋ฏธ ๋ณต์žกํ•จ return (
{/* ๊ธฐ์กด 15๊ฐœ+ ์„ค์ • ํ•ญ๋ชฉ */} {/* + ์ œ์–ด๊ด€๋ฆฌ ์„ค์ • ์ถ”๊ฐ€ ์‹œ ๋”์šฑ ๋ณต์žกํ•ด์ง */}
); ``` **ํ•ด๊ฒฐ๋ฐฉ์•ˆ:** 1. **ํƒญ ๊ตฌ์กฐ๋กœ ๋ถ„๋ฆฌ** ```typescript ๊ธฐ๋ณธ ์„ค์ • ์ œ์–ด๊ด€๋ฆฌ ๊ณ ๊ธ‰ ์„ค์ • {/* ๊ธฐ์กด ์„ค์ • */} {/* ์ œ์–ด๊ด€๋ฆฌ ์„ค์ • */} ``` 2. **๋‹จ๊ณ„๋ณ„ ์„ค์ • ๋งˆ๋ฒ•์‚ฌ** ```typescript const DataflowConfigWizard = () => { const [step, setStep] = useState(1); // 1๋‹จ๊ณ„: ํ™œ์„ฑํ™” ์—ฌ๋ถ€ // 2๋‹จ๊ณ„: ์‹คํ–‰ ํƒ€์ด๋ฐ // 3๋‹จ๊ณ„: ์ œ์–ด ๋ชจ๋“œ // 4๋‹จ๊ณ„: ์ƒ์„ธ ์„ค์ • }; ``` ### 5. **ํƒ€์ž… ์•ˆ์ „์„ฑ ๋ฌธ์ œ** **๋ฌธ์ œ์ :** - ๊ธฐ์กด ButtonTypeConfig์— ์ƒˆ๋กœ์šด ํ•„๋“œ ์ถ”๊ฐ€๋กœ ์ธํ•œ ํ˜ธํ™˜์„ฑ ๋ฌธ์ œ - ๋Ÿฐํƒ€์ž„ ์˜ค๋ฅ˜ ๊ฐ€๋Šฅ์„ฑ **ํ˜„์žฌ ํƒ€์ž… ๊ตฌ์กฐ ๋ฌธ์ œ:** ```typescript // ๊ธฐ์กด ์ฝ”๋“œ๋“ค์ด ButtonTypeConfig์˜ ์ƒˆ ํ•„๋“œ๋ฅผ ๋ชจ๋ฆ„ const config = component.webTypeConfig; // enableDataflowControl ์—†์„ ์ˆ˜ ์žˆ์Œ if (config.enableDataflowControl) { // undefined ์ฒดํฌ ํ•„์š” ``` **ํ•ด๊ฒฐ๋ฐฉ์•ˆ:** 1. **์ ์ง„์  ํƒ€์ž… ํ™•์žฅ** ```typescript // ๊ธฐ์กด ํƒ€์ž…์€ ์œ ์ง€ํ•˜๊ณ  ์ƒˆ๋กœ์šด ํƒ€์ž… ์ •์˜ interface ExtendedButtonTypeConfig extends ButtonTypeConfig { enableDataflowControl?: boolean; dataflowConfig?: ButtonDataflowConfig; dataflowTiming?: "before" | "after" | "replace"; } // ํƒ€์ž… ๊ฐ€๋“œ ํ•จ์ˆ˜ function hasDataflowConfig( config: ButtonTypeConfig ): config is ExtendedButtonTypeConfig { return "enableDataflowControl" in config; } ``` 2. **๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ํ•จ์ˆ˜** ```typescript const migrateButtonConfig = ( config: ButtonTypeConfig ): ExtendedButtonTypeConfig => { return { ...config, enableDataflowControl: false, // ๊ธฐ๋ณธ๊ฐ’ dataflowConfig: undefined, dataflowTiming: "after", }; }; ``` ### 6. **๋ฒ„์ „ ํ˜ธํ™˜์„ฑ ๋ฌธ์ œ** **๋ฌธ์ œ์ :** - ๊ธฐ์กด ์ €์žฅ๋œ ๋ฒ„ํŠผ ์„ค์ •๊ณผ ์ƒˆ๋กœ์šด ๊ตฌ์กฐ ๊ฐ„ ํ˜ธํ™˜์„ฑ - ์ ์ง„์  ๋ฐฐํฌ ์‹œ ์ผ๋ถ€ ๊ธฐ๋Šฅ ๋ถˆ์ผ์น˜ **ํ•ด๊ฒฐ๋ฐฉ์•ˆ:** 1. **๋ฒ„์ „ ํ•„๋“œ ์ถ”๊ฐ€** ```typescript interface ButtonTypeConfig { version?: "1.0" | "2.0"; // ์ œ์–ด๊ด€๋ฆฌ ์ถ”๊ฐ€ ๋ฒ„์ „ // ...๊ธฐ์กด ํ•„๋“œ๋“ค } ``` 2. **์ž๋™ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜** ```typescript const migrateButtonConfig = (config: any) => { if (!config.version || config.version === "1.0") { return { ...config, version: "2.0", enableDataflowControl: false, dataflowConfig: undefined, }; } return config; }; ``` ## ๐Ÿšซ ๋ณด์•ˆ ๊ด€๋ จ ๋ฌธ์ œ์  ### 7. **๊ถŒํ•œ ๊ฒ€์ฆ ๋ถ€์žฌ** **๋ฌธ์ œ์ :** - ์ œ์–ด๊ด€๋ฆฌ ์‹คํ–‰ ์‹œ ์ถ”๊ฐ€์ ์ธ ๊ถŒํ•œ ๊ฒ€์ฆ ์—†์Œ - ์‚ฌ์šฉ์ž๊ฐ€ ์„ค์ •ํ•œ ์ œ์–ด๊ด€๋ฆฌ๋ฅผ ํ†ตํ•ด ์˜๋„์น˜ ์•Š์€ ๋ฐ์ดํ„ฐ ์กฐ์ž‘ ๊ฐ€๋Šฅ **ํ•ด๊ฒฐ๋ฐฉ์•ˆ:** 1. **์ œ์–ด๊ด€๋ฆฌ ๊ถŒํ•œ ์ฒด๊ณ„** ```typescript interface DataflowPermission { canExecuteDataflow: boolean; allowedTables: string[]; allowedActions: ("insert" | "update" | "delete")[]; } const checkDataflowPermission = async ( userId: string, dataflowConfig: ButtonDataflowConfig ): Promise => { // ์‚ฌ์šฉ์ž๋ณ„ ์ œ์–ด๊ด€๋ฆฌ ๊ถŒํ•œ ๊ฒ€์ฆ }; ``` 2. **์‹คํ–‰ ๋กœ๊ทธ ๋ฐ ๊ฐ์‚ฌ** ```typescript const logDataflowExecution = async ( userId: string, buttonId: string, dataflowResult: ExecutionResult ) => { await prisma.dataflow_audit_log.create({ data: { user_id: userId, button_id: buttonId, executed_actions: dataflowResult.executedActions, execution_time: dataflowResult.executionTime, timestamp: new Date(), }, }); }; ``` ### 8. **SQL ์ธ์ ์…˜ ์œ„ํ—˜** **๋ฌธ์ œ์ :** - ๊ณ ๊ธ‰ ๋ชจ๋“œ์—์„œ ์‚ฌ์šฉ์ž๊ฐ€ ์ง์ ‘ ์กฐ๊ฑด ์„ค์ • ์‹œ SQL ์ธ์ ์…˜ ๊ฐ€๋Šฅ์„ฑ - ๋™์  ํ…Œ์ด๋ธ”๋ช…, ํ•„๋“œ๋ช… ์ฒ˜๋ฆฌ ์‹œ ๋ณด์•ˆ ์ทจ์•ฝ์  **ํ•ด๊ฒฐ๋ฐฉ์•ˆ:** 1. **ํ™”์ดํŠธ๋ฆฌ์ŠคํŠธ ๊ธฐ๋ฐ˜ ๊ฒ€์ฆ** ```typescript const ALLOWED_TABLES = ["user_info", "order_master" /* ... */]; const ALLOWED_OPERATORS = ["=", "!=", ">", "<", ">=", "<=", "LIKE"]; const validateDataflowConfig = (config: ButtonDataflowConfig) => { if (config.directControl) { if (!ALLOWED_TABLES.includes(config.directControl.sourceTable)) { throw new Error("ํ—ˆ์šฉ๋˜์ง€ ์•Š์€ ํ…Œ์ด๋ธ”์ž…๋‹ˆ๋‹ค."); } // ์ถ”๊ฐ€ ๊ฒ€์ฆ... } }; ``` 2. **ํŒŒ๋ผ๋ฏธํ„ฐํ™”๋œ ์ฟผ๋ฆฌ ๊ฐ•์ œ** ```typescript // ๋ชจ๋“  ๋™์  ์ฟผ๋ฆฌ๋ฅผ ํŒŒ๋ผ๋ฏธํ„ฐํ™” const executeCondition = async (condition: DataflowCondition, data: any) => { const query = `SELECT * FROM ${tableName} WHERE ${fieldName} ${operator} $1`; return await prisma.$queryRaw(query, condition.value); }; ``` ## ๐Ÿ’ก ๊ถŒ์žฅ ํ•ด๊ฒฐ ์ „๋žต ### Phase 1: ์•ˆ์ „ํ•œ ์‹œ์ž‘ (MVP) 1. **๊ฐ„ํŽธ ๋ชจ๋“œ๋งŒ ๊ตฌํ˜„** (๊ธฐ์กด ๊ด€๊ณ„๋„ ์„ ํƒ) 2. **"after" ํƒ€์ด๋ฐ๋งŒ ์ง€์›** (๊ธฐ์กด ์•ก์…˜ ํ›„ ์‹คํ–‰) 3. **๊ธฐ๋ณธ์ ์ธ ์„ฑ๋Šฅ ์ตœ์ ํ™”** (์บ์‹ฑ, ์ธ๋ฑ์Šค) 4. **์ƒ์„ธํ•œ ๋กœ๊น… ๋ฐ ๋ชจ๋‹ˆํ„ฐ๋ง** ์ถ”๊ฐ€ ### Phase 2: ์ ์ง„์  ํ™•์žฅ 1. **๊ณ ๊ธ‰ ๋ชจ๋“œ ์ถ”๊ฐ€** (๊ถŒํ•œ ๊ฒ€์ฆ ๊ฐ•ํ™”) 2. **"before", "replace" ํƒ€์ด๋ฐ ์ง€์›** 3. **์„ฑ๋Šฅ ์ตœ์ ํ™” ๊ณ ๋„ํ™”** (๋น„๋™๊ธฐ ์‹คํ–‰, ํ์ž‰) 4. **UI ๊ฐœ์„ ** (ํƒญ, ๋งˆ๋ฒ•์‚ฌ) ### Phase 3: ๊ณ ๋„ํ™” 1. **๋ฐฐ์น˜ ์ฒ˜๋ฆฌ ์ง€์›** 2. **๋ณต์žกํ•œ ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง ์ง€์›** 3. **AI ๊ธฐ๋ฐ˜ ์„ค์ • ์ถ”์ฒœ** 4. **์„ฑ๋Šฅ ๋Œ€์‹œ๋ณด๋“œ** ### ๋ชจ๋‹ˆํ„ฐ๋ง ์ง€ํ‘œ ```typescript interface DataflowMetrics { averageExecutionTime: number; errorRate: number; memoryUsage: number; cacheHitRate: number; userSatisfactionScore: number; } ``` ์ด๋Ÿฌํ•œ ๋ฌธ์ œ์ ๋“ค์„ ์‚ฌ์ „์— ๊ณ ๋ คํ•˜์—ฌ ์„ค๊ณ„ํ•˜๋ฉด ์•ˆ์ •์ ์ด๊ณ  ํ™•์žฅ ๊ฐ€๋Šฅํ•œ ์‹œ์Šคํ…œ์„ ๊ตฌ์ถ•ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.