데이터흐름 문서 생성
This commit is contained in:
parent
70855c09c6
commit
e4e11fa490
|
|
@ -0,0 +1,670 @@
|
||||||
|
# 데이터 흐름 관리 시스템 설계
|
||||||
|
|
||||||
|
## 📋 목차
|
||||||
|
|
||||||
|
1. [시스템 개요](#시스템-개요)
|
||||||
|
2. [핵심 기능](#핵심-기능)
|
||||||
|
3. [데이터베이스 설계](#데이터베이스-설계)
|
||||||
|
4. [프론트엔드 설계](#프론트엔드-설계)
|
||||||
|
5. [API 설계](#api-설계)
|
||||||
|
6. [사용 시나리오](#사용-시나리오)
|
||||||
|
7. [구현 계획](#구현-계획)
|
||||||
|
|
||||||
|
## 🎯 시스템 개요
|
||||||
|
|
||||||
|
### 데이터 흐름 관리 시스템이란?
|
||||||
|
|
||||||
|
데이터 흐름 관리 시스템은 회사별로 화면들 간의 데이터 흐름과 관계를 시각적으로 설계하고 관리할 수 있는 시스템입니다. React Flow 라이브러리를 활용하여 직관적인 노드 기반 인터페이스로 1:1, 1:N, N:1, N:N 관계를 지원하며, 다양한 연결 방식과 종류로 복합적인 데이터 흐름을 설계할 수 있습니다.
|
||||||
|
|
||||||
|
### 주요 특징
|
||||||
|
|
||||||
|
- **React Flow 기반 인터페이스**: 직관적인 노드와 엣지 기반 시각적 설계
|
||||||
|
- **회사별 관계 관리**: 사용자 회사 코드에 따른 화면 관계 접근 제어
|
||||||
|
- **시각적 관계 설계**: 드래그앤드롭으로 화면 노드 배치 및 필드 간 연결
|
||||||
|
- **다양한 관계 타입**: 1:1, 1:N, N:1, N:N 관계 지원
|
||||||
|
- **연결 종류별 세부 설정**: 단순 키값, 데이터 저장, 외부 호출
|
||||||
|
- **실시간 시뮬레이션**: 설계한 관계의 데이터 흐름 시뮬레이션
|
||||||
|
- **중계 테이블 자동 생성**: N:N 관계에서 중계 테이블 자동 생성
|
||||||
|
- **인터랙티브 캔버스**: 줌, 팬, 미니맵 등 React Flow의 고급 기능 활용
|
||||||
|
|
||||||
|
### 지원하는 관계 타입
|
||||||
|
|
||||||
|
- **1:1 (One to One)**: 한 화면의 필드와 다른 화면의 필드가 1:1로 연결
|
||||||
|
- **1:N (One to Many)**: 한 화면의 필드가 여러 화면의 필드와 연결
|
||||||
|
- **N:1 (Many to One)**: 여러 화면의 필드가 한 화면의 필드와 연결
|
||||||
|
- **N:N (Many to Many)**: 여러 화면의 필드가 여러 화면의 필드와 연결
|
||||||
|
|
||||||
|
### 지원하는 연결 종류
|
||||||
|
|
||||||
|
- **단순 키값 연결**: 중계 테이블을 통한 참조 관계
|
||||||
|
- **데이터 저장**: 필드 매핑을 통한 데이터 저장
|
||||||
|
- **외부 호출**: API, 이메일, 웹훅 등을 통한 외부 시스템 연동
|
||||||
|
|
||||||
|
## 🚀 핵심 기능
|
||||||
|
|
||||||
|
### 1. React Flow 기반 화면 노드 관리
|
||||||
|
|
||||||
|
- **화면 추가**: 회사별 화면 목록에서 관계를 설정할 화면들을 React Flow 캔버스에 추가
|
||||||
|
- **화면 배치**: 드래그앤드롭으로 화면 노드를 원하는 위치에 배치
|
||||||
|
- **화면 이동**: React Flow의 내장 기능으로 화면 노드 자유롭게 이동
|
||||||
|
- **노드 선택**: 단일 또는 다중 노드 선택 지원
|
||||||
|
- **자동 정렬**: React Flow의 레이아웃 알고리즘을 활용한 자동 정렬
|
||||||
|
|
||||||
|
### 2. React Flow 기반 필드 간 연결 설정
|
||||||
|
|
||||||
|
- **필드 선택**: 첫 번째 화면의 필드를 클릭하여 연결 시작
|
||||||
|
- **대상 필드 선택**: 두 번째 화면의 필드를 클릭하여 연결 대상 지정
|
||||||
|
- **드래그 연결**: React Flow의 핸들(Handle)을 드래그하여 시각적 연결
|
||||||
|
- **관계 타입 선택**: 1:1, 1:N, N:1, N:N 중 선택
|
||||||
|
- **연결 종류 선택**: 단순 키값, 데이터 저장, 외부 호출 중 선택
|
||||||
|
- **엣지 커스터마이징**: 연결 타입별 색상, 스타일, 라벨 설정
|
||||||
|
|
||||||
|
### 3. 연결 종류별 세부 설정
|
||||||
|
|
||||||
|
#### 단순 키값 연결
|
||||||
|
|
||||||
|
- **중계 테이블명**: 자동 생성 또는 사용자 정의
|
||||||
|
- **연결 규칙**: 중계 테이블 생성 및 관리 규칙
|
||||||
|
- **참조 무결성**: 외래키 제약조건 설정
|
||||||
|
|
||||||
|
#### 데이터 저장
|
||||||
|
|
||||||
|
- **필드 매핑**: 소스 필드와 대상 필드 매핑 설정
|
||||||
|
- **저장 조건**: 데이터 저장 조건 정의
|
||||||
|
- **데이터 변환**: 필드 값 변환 규칙
|
||||||
|
|
||||||
|
#### 외부 호출
|
||||||
|
|
||||||
|
- **REST API**: API URL, HTTP Method, Headers, Body Template
|
||||||
|
- **이메일**: SMTP 서버, 발신자, 수신자, 제목/본문 템플릿
|
||||||
|
- **웹훅**: 웹훅 URL, Payload 형식, Payload 템플릿
|
||||||
|
- **FTP**: FTP 서버, 업로드 경로, 파일명 템플릿
|
||||||
|
- **메시지 큐**: 큐 시스템, 큐 이름, 메시지 형식
|
||||||
|
|
||||||
|
### 4. React Flow 기반 시각적 관계 관리
|
||||||
|
|
||||||
|
- **엣지 렌더링**: React Flow의 커스텀 엣지로 화면 간 관계를 시각적으로 표현
|
||||||
|
- **관계 타입별 스타일링**: 연결 종류에 따른 색상, 선 스타일, 라벨 구분
|
||||||
|
- **인터랙티브 캔버스**: 줌, 팬, 미니맵을 통한 대규모 다이어그램 탐색
|
||||||
|
- **실시간 시뮬레이션**: 데이터 흐름 애니메이션 및 시뮬레이션
|
||||||
|
- **관계 검증**: 연결 가능성 및 무결성 검증
|
||||||
|
- **엣지 편집**: 연결선 클릭으로 관계 설정 수정
|
||||||
|
|
||||||
|
### 5. 관계 통계 및 관리
|
||||||
|
|
||||||
|
- **연결 통계**: 관계 타입별 연결 수 표시
|
||||||
|
- **중계 테이블 관리**: 생성된 중계 테이블 목록 및 관리
|
||||||
|
- **관계 목록**: 생성된 모든 관계 목록 조회
|
||||||
|
- **관계 삭제**: 불필요한 관계 삭제
|
||||||
|
|
||||||
|
## 🗄️ 데이터베이스 설계
|
||||||
|
|
||||||
|
### 1. 화면 관계 테이블
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- 화면 간 관계 정의
|
||||||
|
CREATE TABLE screen_relationships (
|
||||||
|
relationship_id SERIAL PRIMARY KEY,
|
||||||
|
relationship_name VARCHAR(200) NOT NULL,
|
||||||
|
from_screen_id INTEGER NOT NULL,
|
||||||
|
from_field_name VARCHAR(100) NOT NULL,
|
||||||
|
to_screen_id INTEGER NOT NULL,
|
||||||
|
to_field_name VARCHAR(100) NOT NULL,
|
||||||
|
relationship_type VARCHAR(20) NOT NULL, -- 'one-to-one', 'one-to-many', 'many-to-one', 'many-to-many'
|
||||||
|
connection_type VARCHAR(20) NOT NULL, -- 'simple-key', 'data-save', 'external-call'
|
||||||
|
company_code VARCHAR(50) NOT NULL,
|
||||||
|
settings JSONB, -- 연결 종류별 세부 설정
|
||||||
|
is_active CHAR(1) DEFAULT 'Y',
|
||||||
|
created_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
created_by VARCHAR(50),
|
||||||
|
updated_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
updated_by VARCHAR(50),
|
||||||
|
|
||||||
|
-- 외래키 제약조건
|
||||||
|
CONSTRAINT fk_screen_relationships_from_screen
|
||||||
|
FOREIGN KEY (from_screen_id) REFERENCES screen_definitions(screen_id),
|
||||||
|
CONSTRAINT fk_screen_relationships_to_screen
|
||||||
|
FOREIGN KEY (to_screen_id) REFERENCES screen_definitions(screen_id)
|
||||||
|
);
|
||||||
|
|
||||||
|
-- 회사 코드 인덱스
|
||||||
|
CREATE INDEX idx_screen_relationships_company_code ON screen_relationships(company_code);
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 중계 테이블 관리
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- 중계 테이블 정의
|
||||||
|
CREATE TABLE bridge_tables (
|
||||||
|
bridge_id SERIAL PRIMARY KEY,
|
||||||
|
bridge_name VARCHAR(200) NOT NULL,
|
||||||
|
table_name VARCHAR(100) NOT NULL,
|
||||||
|
relationship_id INTEGER NOT NULL,
|
||||||
|
company_code VARCHAR(50) NOT NULL,
|
||||||
|
description TEXT,
|
||||||
|
is_active CHAR(1) DEFAULT 'Y',
|
||||||
|
created_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
created_by VARCHAR(50),
|
||||||
|
|
||||||
|
-- 외래키 제약조건
|
||||||
|
CONSTRAINT fk_bridge_tables_relationship
|
||||||
|
FOREIGN KEY (relationship_id) REFERENCES screen_relationships(relationship_id)
|
||||||
|
);
|
||||||
|
|
||||||
|
-- 회사 코드 인덱스
|
||||||
|
CREATE INDEX idx_bridge_tables_company_code ON bridge_tables(company_code);
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. 외부 호출 설정
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- 외부 호출 설정
|
||||||
|
CREATE TABLE external_call_configs (
|
||||||
|
config_id SERIAL PRIMARY KEY,
|
||||||
|
relationship_id INTEGER NOT NULL,
|
||||||
|
call_type VARCHAR(50) NOT NULL, -- 'rest-api', 'email', 'webhook', 'ftp', 'queue'
|
||||||
|
parameters JSONB NOT NULL, -- 호출 유형별 파라미터
|
||||||
|
is_active CHAR(1) DEFAULT 'Y',
|
||||||
|
created_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
created_by VARCHAR(50),
|
||||||
|
|
||||||
|
-- 외래키 제약조건
|
||||||
|
CONSTRAINT fk_external_call_configs_relationship
|
||||||
|
FOREIGN KEY (relationship_id) REFERENCES screen_relationships(relationship_id)
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. 테이블 간 연계 관계
|
||||||
|
|
||||||
|
```
|
||||||
|
screen_definitions (화면 정의)
|
||||||
|
↓ (1:N)
|
||||||
|
screen_relationships (화면 관계)
|
||||||
|
↓ (1:N)
|
||||||
|
bridge_tables (중계 테이블)
|
||||||
|
↓ (1:N)
|
||||||
|
external_call_configs (외부 호출 설정)
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🎨 프론트엔드 설계
|
||||||
|
|
||||||
|
### 1. React Flow 기반 메인 컴포넌트
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// DataFlowDesigner.tsx
|
||||||
|
import ReactFlow, {
|
||||||
|
Node,
|
||||||
|
Edge,
|
||||||
|
Controls,
|
||||||
|
Background,
|
||||||
|
MiniMap,
|
||||||
|
useNodesState,
|
||||||
|
useEdgesState,
|
||||||
|
addEdge,
|
||||||
|
Connection,
|
||||||
|
EdgeChange,
|
||||||
|
NodeChange,
|
||||||
|
} from "reactflow";
|
||||||
|
import "reactflow/dist/style.css";
|
||||||
|
|
||||||
|
interface DataFlowDesignerProps {
|
||||||
|
companyCode: string;
|
||||||
|
onSave?: (relationships: ScreenRelationship[]) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const DataFlowDesigner: React.FC<DataFlowDesignerProps> = ({
|
||||||
|
companyCode,
|
||||||
|
onSave,
|
||||||
|
}) => {
|
||||||
|
const [nodes, setNodes, onNodesChange] = useNodesState([]);
|
||||||
|
const [edges, setEdges, onEdgesChange] = useEdgesState([]);
|
||||||
|
const [selectedField, setSelectedField] = useState<FieldSelection | null>(
|
||||||
|
null
|
||||||
|
);
|
||||||
|
const [pendingConnection, setPendingConnection] =
|
||||||
|
useState<PendingConnection | null>(null);
|
||||||
|
|
||||||
|
const onConnect = useCallback(
|
||||||
|
(params: Connection) => {
|
||||||
|
setEdges((eds) => addEdge(params, eds));
|
||||||
|
},
|
||||||
|
[setEdges]
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="data-flow-designer h-screen">
|
||||||
|
<div className="flex h-full">
|
||||||
|
{/* 사이드바 */}
|
||||||
|
<div className="w-80 bg-gray-50 border-r">
|
||||||
|
<ScreenSelector
|
||||||
|
companyCode={companyCode}
|
||||||
|
onScreenAdd={handleScreenAdd}
|
||||||
|
/>
|
||||||
|
<ConnectionStatus
|
||||||
|
selectedField={selectedField}
|
||||||
|
onCancel={handleCancelConnection}
|
||||||
|
/>
|
||||||
|
<ConnectionStats relationships={relationships} />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* React Flow 캔버스 */}
|
||||||
|
<div className="flex-1">
|
||||||
|
<ReactFlow
|
||||||
|
nodes={nodes}
|
||||||
|
edges={edges}
|
||||||
|
onNodesChange={onNodesChange}
|
||||||
|
onEdgesChange={onEdgesChange}
|
||||||
|
onConnect={onConnect}
|
||||||
|
onNodeClick={handleNodeClick}
|
||||||
|
onEdgeClick={handleEdgeClick}
|
||||||
|
nodeTypes={nodeTypes}
|
||||||
|
edgeTypes={edgeTypes}
|
||||||
|
fitView
|
||||||
|
>
|
||||||
|
<Controls />
|
||||||
|
<MiniMap />
|
||||||
|
<Background variant="dots" gap={12} size={1} />
|
||||||
|
</ReactFlow>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<ConnectionSetupModal
|
||||||
|
isOpen={!!pendingConnection}
|
||||||
|
connection={pendingConnection}
|
||||||
|
onConfirm={handleConfirmConnection}
|
||||||
|
onCancel={handleCancelConnection}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. React Flow 화면 노드 컴포넌트
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// ScreenNode.tsx
|
||||||
|
import { Handle, Position } from "reactflow";
|
||||||
|
|
||||||
|
interface ScreenNodeData {
|
||||||
|
screen: ScreenDefinition;
|
||||||
|
onFieldClick: (screenId: string, fieldName: string) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ScreenNode: React.FC<{ data: ScreenNodeData }> = ({ data }) => {
|
||||||
|
const { screen, onFieldClick } = data;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="bg-white border-2 border-gray-300 rounded-lg shadow-lg min-w-80">
|
||||||
|
{/* 노드 헤더 */}
|
||||||
|
<div className="bg-blue-500 text-white p-3 rounded-t-lg">
|
||||||
|
<div className="font-bold text-sm">{screen.screenName}</div>
|
||||||
|
<div className="text-xs opacity-90">{screen.screenCode}</div>
|
||||||
|
<div className="text-xs opacity-75">테이블: {screen.tableName}</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 필드 목록 */}
|
||||||
|
<div className="p-3">
|
||||||
|
<div className="text-xs font-semibold text-gray-600 mb-2">
|
||||||
|
필드 목록 ({screen.fields.length}개)
|
||||||
|
</div>
|
||||||
|
<div className="space-y-1">
|
||||||
|
{screen.fields.map((field) => (
|
||||||
|
<div
|
||||||
|
key={field.name}
|
||||||
|
className="flex items-center justify-between p-2 hover:bg-gray-50 rounded cursor-pointer"
|
||||||
|
onClick={() => onFieldClick(screen.screenId, field.name)}
|
||||||
|
>
|
||||||
|
<div className="flex-1">
|
||||||
|
<div className="text-sm font-medium">{field.name}</div>
|
||||||
|
<div className="text-xs text-gray-500">{field.description}</div>
|
||||||
|
</div>
|
||||||
|
<div className="text-xs text-blue-600 font-mono">
|
||||||
|
{field.type}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* React Flow 핸들 */}
|
||||||
|
<Handle
|
||||||
|
type="source"
|
||||||
|
position={Position.Right}
|
||||||
|
className="w-3 h-3 bg-blue-500"
|
||||||
|
/>
|
||||||
|
<Handle
|
||||||
|
type="target"
|
||||||
|
position={Position.Left}
|
||||||
|
className="w-3 h-3 bg-green-500"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 노드 타입 정의
|
||||||
|
export const nodeTypes = {
|
||||||
|
screenNode: ScreenNode,
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. 연결 설정 모달
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// ConnectionSetupModal.tsx
|
||||||
|
interface ConnectionSetupModalProps {
|
||||||
|
isOpen: boolean;
|
||||||
|
connection: PendingConnection | null;
|
||||||
|
onConfirm: (config: ConnectionConfig) => void;
|
||||||
|
onCancel: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ConnectionSetupModal: React.FC<ConnectionSetupModalProps> = ({
|
||||||
|
isOpen,
|
||||||
|
connection,
|
||||||
|
onConfirm,
|
||||||
|
onCancel,
|
||||||
|
}) => {
|
||||||
|
const [connectionType, setConnectionType] =
|
||||||
|
useState<ConnectionType>("simple-key");
|
||||||
|
const [relationshipType, setRelationshipType] =
|
||||||
|
useState<RelationshipType>("one-to-one");
|
||||||
|
const [settings, setSettings] = useState<ConnectionSettings>({});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Dialog open={isOpen} onOpenChange={onCancel}>
|
||||||
|
<DialogContent className="max-w-2xl">
|
||||||
|
<DialogHeader>
|
||||||
|
<DialogTitle>필드 연결 설정</DialogTitle>
|
||||||
|
</DialogHeader>
|
||||||
|
|
||||||
|
<div className="space-y-6">
|
||||||
|
{/* 연결 정보 표시 */}
|
||||||
|
<ConnectionInfo connection={connection} />
|
||||||
|
|
||||||
|
{/* 관계 타입 선택 */}
|
||||||
|
<RelationshipTypeSelector
|
||||||
|
value={relationshipType}
|
||||||
|
onChange={setRelationshipType}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* 연결 종류 선택 */}
|
||||||
|
<ConnectionTypeSelector
|
||||||
|
value={connectionType}
|
||||||
|
onChange={setConnectionType}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* 연결 종류별 세부 설정 */}
|
||||||
|
<ConnectionSettingsPanel
|
||||||
|
type={connectionType}
|
||||||
|
settings={settings}
|
||||||
|
onChange={setSettings}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<DialogFooter>
|
||||||
|
<Button onClick={onCancel}>취소</Button>
|
||||||
|
<Button
|
||||||
|
onClick={() =>
|
||||||
|
onConfirm({ connectionType, relationshipType, settings })
|
||||||
|
}
|
||||||
|
>
|
||||||
|
연결 생성
|
||||||
|
</Button>
|
||||||
|
</DialogFooter>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. React Flow 엣지 컴포넌트
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// CustomEdge.tsx
|
||||||
|
import {
|
||||||
|
EdgeProps,
|
||||||
|
getBezierPath,
|
||||||
|
EdgeLabelRenderer,
|
||||||
|
BaseEdge,
|
||||||
|
} from "reactflow";
|
||||||
|
|
||||||
|
interface CustomEdgeData {
|
||||||
|
relationshipType: string;
|
||||||
|
connectionType: string;
|
||||||
|
label?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const CustomEdge: React.FC<EdgeProps<CustomEdgeData>> = ({
|
||||||
|
id,
|
||||||
|
sourceX,
|
||||||
|
sourceY,
|
||||||
|
targetX,
|
||||||
|
targetY,
|
||||||
|
sourcePosition,
|
||||||
|
targetPosition,
|
||||||
|
data,
|
||||||
|
markerEnd,
|
||||||
|
}) => {
|
||||||
|
const [edgePath, labelX, labelY] = getBezierPath({
|
||||||
|
sourceX,
|
||||||
|
sourceY,
|
||||||
|
sourcePosition,
|
||||||
|
targetX,
|
||||||
|
targetY,
|
||||||
|
targetPosition,
|
||||||
|
});
|
||||||
|
|
||||||
|
const getEdgeColor = (connectionType: string) => {
|
||||||
|
switch (connectionType) {
|
||||||
|
case "simple-key":
|
||||||
|
return "#3B82F6"; // 파란색
|
||||||
|
case "data-save":
|
||||||
|
return "#10B981"; // 초록색
|
||||||
|
case "external-call":
|
||||||
|
return "#F59E0B"; // 주황색
|
||||||
|
default:
|
||||||
|
return "#6B7280"; // 회색
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const getEdgeStyle = (connectionType: string) => {
|
||||||
|
switch (connectionType) {
|
||||||
|
case "simple-key":
|
||||||
|
return { strokeWidth: 2, strokeDasharray: "5,5" };
|
||||||
|
case "data-save":
|
||||||
|
return { strokeWidth: 3 };
|
||||||
|
case "external-call":
|
||||||
|
return { strokeWidth: 2, strokeDasharray: "10,5" };
|
||||||
|
default:
|
||||||
|
return { strokeWidth: 2 };
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<BaseEdge
|
||||||
|
id={id}
|
||||||
|
path={edgePath}
|
||||||
|
markerEnd={markerEnd}
|
||||||
|
style={{
|
||||||
|
stroke: getEdgeColor(data?.connectionType || ""),
|
||||||
|
...getEdgeStyle(data?.connectionType || ""),
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<EdgeLabelRenderer>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
position: "absolute",
|
||||||
|
transform: `translate(-50%, -50%) translate(${labelX}px,${labelY}px)`,
|
||||||
|
background: "white",
|
||||||
|
padding: "4px 8px",
|
||||||
|
borderRadius: "4px",
|
||||||
|
fontSize: "12px",
|
||||||
|
fontWeight: 500,
|
||||||
|
border: "1px solid #E5E7EB",
|
||||||
|
boxShadow: "0 1px 3px rgba(0, 0, 0, 0.1)",
|
||||||
|
}}
|
||||||
|
className="nodrag nopan"
|
||||||
|
>
|
||||||
|
{data?.label || data?.relationshipType}
|
||||||
|
</div>
|
||||||
|
</EdgeLabelRenderer>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 엣지 타입 정의
|
||||||
|
export const edgeTypes = {
|
||||||
|
customEdge: CustomEdge,
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🌐 API 설계
|
||||||
|
|
||||||
|
### 1. 화면 관계 관리 API
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// 화면 관계 생성
|
||||||
|
POST /api/screen-relationships
|
||||||
|
Body: {
|
||||||
|
relationshipName: string;
|
||||||
|
fromScreenId: number;
|
||||||
|
fromFieldName: string;
|
||||||
|
toScreenId: number;
|
||||||
|
toFieldName: string;
|
||||||
|
relationshipType: 'one-to-one' | 'one-to-many' | 'many-to-one' | 'many-to-many';
|
||||||
|
connectionType: 'simple-key' | 'data-save' | 'external-call';
|
||||||
|
settings: ConnectionSettings;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 화면 관계 목록 조회 (회사별)
|
||||||
|
GET /api/screen-relationships?companyCode=COMP001
|
||||||
|
|
||||||
|
// 화면 관계 수정
|
||||||
|
PUT /api/screen-relationships/:id
|
||||||
|
|
||||||
|
// 화면 관계 삭제
|
||||||
|
DELETE /api/screen-relationships/:id
|
||||||
|
|
||||||
|
// 관계 시뮬레이션
|
||||||
|
POST /api/screen-relationships/:id/simulate
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 중계 테이블 관리 API
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// 중계 테이블 생성
|
||||||
|
POST /api/bridge-tables
|
||||||
|
Body: {
|
||||||
|
bridgeName: string;
|
||||||
|
tableName: string;
|
||||||
|
relationshipId: number;
|
||||||
|
description?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 중계 테이블 목록 조회
|
||||||
|
GET /api/bridge-tables?companyCode=COMP001
|
||||||
|
|
||||||
|
// 중계 테이블 삭제
|
||||||
|
DELETE /api/bridge-tables/:id
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. 외부 호출 설정 API
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// 외부 호출 설정 생성
|
||||||
|
POST /api/external-call-configs
|
||||||
|
Body: {
|
||||||
|
relationshipId: number;
|
||||||
|
callType: 'rest-api' | 'email' | 'webhook' | 'ftp' | 'queue';
|
||||||
|
parameters: Record<string, any>;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 외부 호출 설정 조회
|
||||||
|
GET /api/external-call-configs?relationshipId=123
|
||||||
|
|
||||||
|
// 외부 호출 설정 수정
|
||||||
|
PUT /api/external-call-configs/:id
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🎬 사용 시나리오
|
||||||
|
|
||||||
|
### 1. 기본 관계 설정
|
||||||
|
|
||||||
|
1. **화면 추가**: 회사별 화면 목록에서 관계를 설정할 화면들을 캔버스에 추가
|
||||||
|
2. **필드 선택**: 첫 번째 화면의 필드를 클릭하여 연결 시작
|
||||||
|
3. **대상 필드 선택**: 두 번째 화면의 필드를 클릭하여 연결 대상 지정
|
||||||
|
4. **관계 설정**: 관계 타입과 연결 종류를 선택하고 세부 설정 구성
|
||||||
|
5. **연결 생성**: 설정 완료 후 연결 생성
|
||||||
|
|
||||||
|
### 2. 복합 데이터 흐름 설계
|
||||||
|
|
||||||
|
1. **다중 화면 배치**: 관련된 여러 화면을 캔버스에 배치
|
||||||
|
2. **다양한 연결 타입**: 단순 키값, 데이터 저장, 외부 호출을 조합
|
||||||
|
3. **중계 테이블 활용**: N:N 관계에서 중계 테이블 자동 생성
|
||||||
|
4. **시각적 검증**: 연결선과 색상으로 관계 유형 구분
|
||||||
|
|
||||||
|
### 3. 외부 시스템 연동
|
||||||
|
|
||||||
|
1. **API 연결**: REST API 호출을 통한 외부 시스템 연동
|
||||||
|
2. **이메일 알림**: 데이터 변경 시 이메일 자동 전송
|
||||||
|
3. **웹훅 설정**: 실시간 데이터 동기화
|
||||||
|
4. **메시지 큐**: 비동기 데이터 처리
|
||||||
|
|
||||||
|
## 📅 구현 계획
|
||||||
|
|
||||||
|
### Phase 1: React Flow 기본 설정 (1주)
|
||||||
|
|
||||||
|
- [ ] React Flow 라이브러리 설치 및 설정
|
||||||
|
- [ ] 기본 노드와 엣지 컴포넌트 구현
|
||||||
|
- [ ] 화면 노드 컴포넌트 구현
|
||||||
|
- [ ] 기본 연결선 그리기
|
||||||
|
|
||||||
|
### Phase 2: 관계 설정 기능 (2주)
|
||||||
|
|
||||||
|
- [ ] 1:1, 1:N 관계 설정
|
||||||
|
- [ ] 단순 키값 연결
|
||||||
|
- [ ] 연결 설정 모달
|
||||||
|
- [ ] 노드 간 드래그앤드롭 연결
|
||||||
|
|
||||||
|
### Phase 3: 고급 연결 타입 (2-3주)
|
||||||
|
|
||||||
|
- [ ] 데이터 저장 연결
|
||||||
|
- [ ] 외부 호출 연결
|
||||||
|
- [ ] 중계 테이블 자동 생성
|
||||||
|
- [ ] 커스텀 엣지 스타일링
|
||||||
|
|
||||||
|
### Phase 4: React Flow 고급 기능 (1-2주)
|
||||||
|
|
||||||
|
- [ ] 줌, 팬, 미니맵 기능
|
||||||
|
- [ ] 노드 선택 및 다중 선택
|
||||||
|
- [ ] 키보드 단축키 지원
|
||||||
|
- [ ] 레이아웃 자동 정렬
|
||||||
|
|
||||||
|
### Phase 5: 시각화 및 관리 (1-2주)
|
||||||
|
|
||||||
|
- [ ] 관계 시뮬레이션
|
||||||
|
- [ ] 연결 통계 및 관리
|
||||||
|
- [ ] 관계 검증
|
||||||
|
- [ ] 데이터 흐름 애니메이션
|
||||||
|
|
||||||
|
### Phase 6: 고급 기능 (2-3주)
|
||||||
|
|
||||||
|
- [ ] N:N 관계 지원
|
||||||
|
- [ ] 복합 데이터 흐름
|
||||||
|
- [ ] 외부 시스템 연동
|
||||||
|
- [ ] 성능 최적화
|
||||||
|
|
||||||
|
## 🎯 결론
|
||||||
|
|
||||||
|
**데이터 흐름 관리 시스템**을 통해 ERP 시스템의 화면들 간 데이터 흐름을 시각적으로 설계하고 관리할 수 있습니다. React Flow 라이브러리를 활용한 직관적인 노드 기반 인터페이스와 회사별 권한 관리, 기존 화면관리 시스템과의 완벽한 연동을 통해 체계적인 데이터 관계 관리가 가능합니다.
|
||||||
|
|
||||||
|
### 주요 가치
|
||||||
|
|
||||||
|
- **React Flow 기반 시각적 설계**: 복잡한 데이터 관계를 직관적인 노드와 엣지로 설계
|
||||||
|
- **인터랙티브 캔버스**: 줌, 팬, 미니맵 등 고급 시각화 기능 제공
|
||||||
|
- **회사별 관리**: 각 회사별로 독립적인 관계 관리
|
||||||
|
- **다양한 연결 타입**: 업무 요구사항에 맞는 다양한 연결 방식
|
||||||
|
- **자동화**: 중계 테이블 자동 생성 및 외부 시스템 연동
|
||||||
|
- **확장성**: 새로운 연결 타입과 관계 유형 쉽게 추가
|
||||||
|
- **사용자 친화적**: 드래그앤드롭 기반의 직관적인 사용자 인터페이스
|
||||||
Loading…
Reference in New Issue