Files
sam-docs/projects/mes/00_baseline/BACKEND_FRONTEND_COLLABORATION_GUIDE.md
hskwon 08a8259313 docs: 5130 레거시 분석 문서 및 기존 문서 초기 커밋
- 5130 레거시 시스템 분석 (00_OVERVIEW ~ 08_SAM_COMPARISON)
- MES 프로젝트 문서
- API/프론트엔드 스펙 문서
- 가이드 및 레퍼런스 문서
2025-12-04 18:47:19 +09:00

27 KiB
Raw Blame History

이거 # 백엔드-프론트엔드 협업 가이드

메타데이터 기반 동적 UI 구현 전략

작성 일자: 2025-11-12 목적: 백엔드-프론트엔드 협업을 위한 DB 설계 및 API 구조 논의 대상: 백엔드 개발자 + 프론트엔드 개발자


📋 목차

  1. 현재 상황 분석
  2. 목표 아키텍처
  3. DB 스키마 설계
  4. API 설계
  5. 백엔드-프론트 계약
  6. 논의 포인트

현재 상황 분석

문제 상황

ItemManagement.tsx: 6,521줄 고정 화면

  • 품목 유형별로 완전히 다른 화면 구성
  • 새 필드 추가 시 코드 수정 필요
  • 12개 부품 카테고리마다 다른 폼
  • 유지보수 불가능한 구조

품목 유형별 필드 구성

품목 유형 주요 필드 특이사항
FG (제품) productName, itemName, specification, unit, salesPrice BOM 구성 필요
PT-ASSEMBLY (조립 부품) 12개 카테고리별 다름 guide_rail: installationType, assemblyType, sideSpecWidth
case: assemblyType, material, color
rod: diameter, length, material
등 12가지
PT-BENDING (절곡 부품) material, thickness, bendingDiagram, bendingDetails[] 전개도 + 데이터 테이블
PT-PURCHASED (구매 부품) category1, purchaseSource, specification, leadTime 단순 구매
RM (원자재) material, specification, unit, purchasePrice, supplier 자재 관리
SM (부자재) material, specification, unit, purchasePrice 자재 관리
CS (소모품) specification, unit, purchasePrice 자재 관리

핵심 문제:

  • PT-ASSEMBLY 카테고리 12개 × 평균 5개 필드 = 60개 필드
  • 조건부 렌더링: itemType=PT → partType 표시 → partType=ASSEMBLY → category1 표시 → category1=guide_rail → installationType 표시
  • 모든 조건이 코드에 하드코딩됨

현재 코드 구조

// 6,521줄 중 일부 (의사 코드)
if (formData.itemType === "FG") {
  return (
    <div>
      <Input label="상품명" name="productName" required />
      <Input label="품목명" name="itemName" required />
      <Input label="규격" name="specification" required />
      {/* ... 20개 필드 */}
    </div>
  );
}

if (formData.itemType === "PT" && formData.partType === "ASSEMBLY") {
  if (formData.category1 === "guide_rail") {
    return (
      <div>
        <Select label="설치유형" name="installationType" options={["wall", "ceiling"]} required />
        <Select label="조립유형" name="assemblyType" options={["M", "T", "P", "B", "S"]} required />
        <Input label="사이드 폭" name="sideSpecWidth" type="number" required />
        {/* ... 15개 필드 */}
      </div>
    );
  }
  if (formData.category1 === "case") {
    // 또 다른 15개 필드
  }
  // ... 10개 카테고리 더
}

// ... 총 6,521줄

문제:

  • 새 카테고리 추가 → 코드 수정 (1일 작업)
  • 필드 하나 추가 → 10곳 수정 필요 (4시간 작업)
  • 검증 규칙 변경 → 코드 수정 + 배포

목표 아키텍처

메타데이터 기반 동적 UI

컨셉:

  • DB에 화면 구성 정보 저장 (필드 정의, 섹션, 조건부 규칙)
  • API로 메타데이터 전달 (GET /api/v1/items/metadata)
  • 프론트: 메타데이터 기반 동적 렌더링 (MetaFormBuilder)

장점:

  • 새 카테고리 추가: DB INSERT만 (1시간)
  • 필드 추가: DB INSERT만 (5분)
  • 검증 규칙 변경: DB UPDATE만 (5분)
  • 코드 변경 없음 → 배포 불필요
  • 관리자 화면에서 필드 관리 가능

아키텍처 다이어그램

┌─────────────────────────────────────────────────────────────┐
│                         Database                            │
├─────────────────────────────────────────────────────────────┤
│  item_field_definitions   (필드 메타데이터)                 │
│  item_field_groups        (섹션/그룹)                       │
│  item_field_group_fields  (필드-그룹 매핑)                  │
│  item_render_rules        (조건부 렌더링 규칙)              │
└─────────────────────────────────────────────────────────────┘
                            ↓
                    Backend API (Laravel)
                            ↓
┌─────────────────────────────────────────────────────────────┐
│  GET /api/v1/items/metadata?itemType=FG                     │
│  Response: {                                                │
│    fields: [...],    // 필드 정의 배열                      │
│    groups: [...],    // 섹션 배열                          │
│    rules: [...]      // 조건부 규칙 배열                    │
│  }                                                          │
└─────────────────────────────────────────────────────────────┘
                            ↓
                    Frontend (React)
                            ↓
┌─────────────────────────────────────────────────────────────┐
│  <MetaFormBuilder metadata={metadata} />                    │
│    ├─ MetaFieldRenderer (필드 동적 렌더링)                  │
│    ├─ MetaValidation (검증 동적 실행)                       │
│    └─ MetaRuleEngine (조건부 표시 처리)                     │
└─────────────────────────────────────────────────────────────┘

EAV 패턴 활용

이미 게시판 시스템에서 사용 중:

  • board_settings (게시판 설정 메타데이터)
  • post_custom_field_values (게시물 동적 필드 값)

동일 패턴을 품목 관리에 적용:

  • item_field_definitions (품목 필드 메타데이터)
  • item_field_values (품목 동적 필드 값) ← 필요 시

DB 스키마 설계

1. item_field_definitions (필드 메타데이터)

목적: 모든 필드의 정의를 저장

CREATE TABLE item_field_definitions (
  id BIGINT PRIMARY KEY AUTO_INCREMENT,
  field_key VARCHAR(50) NOT NULL UNIQUE COMMENT '필드 키 (예: productName, installationType)',
  field_label VARCHAR(100) NOT NULL COMMENT '한글 레이블 (예: 상품명, 설치유형)',
  field_type VARCHAR(20) NOT NULL COMMENT 'text, select, number, date, textarea, checkbox, etc.',
  field_options JSON COMMENT 'select/radio의 옵션 배열 [{"value":"FG","label":"제품"}]',
  validation_rules JSON COMMENT '검증 규칙 {"required":true,"min":2,"max":100}',
  placeholder VARCHAR(200) COMMENT '입력 힌트',
  help_text VARCHAR(500) COMMENT '도움말 텍스트',
  display_order INT DEFAULT 0 COMMENT '표시 순서',
  is_active BOOLEAN DEFAULT TRUE COMMENT '활성 여부',
  created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
  updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  INDEX idx_field_key (field_key),
  INDEX idx_is_active (is_active)
) COMMENT='품목 필드 메타데이터';

샘플 데이터:

INSERT INTO item_field_definitions (field_key, field_label, field_type, field_options, validation_rules, display_order) VALUES
('itemType', '품목유형', 'select',
 '[{"value":"FG","label":"제품"},{"value":"PT","label":"부품"},{"value":"RM","label":"원자재"}]',
 '{"required":true}', 1),

('productName', '상품명', 'text', NULL,
 '{"required":true,"min":2,"max":100}', 2),

('installationType', '설치유형', 'select',
 '[{"value":"wall","label":"벽면형"},{"value":"ceiling","label":"천정형"}]',
 '{"required":true}', 10),

('sideSpecWidth', '사이드 폭', 'number', NULL,
 '{"required":true,"min":100,"max":5000}', 11),

('bendingDiagram', '절곡 전개도', 'file', NULL,
 '{"required":true,"accept":"image/*"}', 20);

2. item_field_groups (섹션/그룹)

목적: 필드를 논리적 그룹으로 묶음

CREATE TABLE item_field_groups (
  id BIGINT PRIMARY KEY AUTO_INCREMENT,
  group_key VARCHAR(50) NOT NULL UNIQUE COMMENT '그룹 키 (예: basic_info, spec_info)',
  group_label VARCHAR(100) NOT NULL COMMENT '한글 레이블 (예: 기본 정보, 규격 정보)',
  group_description VARCHAR(500) COMMENT '그룹 설명',
  display_order INT DEFAULT 0 COMMENT '표시 순서',
  is_collapsible BOOLEAN DEFAULT FALSE COMMENT '접기 가능 여부',
  is_collapsed_default BOOLEAN DEFAULT FALSE COMMENT '기본 접힘 상태',
  is_active BOOLEAN DEFAULT TRUE COMMENT '활성 여부',
  created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
  updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  INDEX idx_group_key (group_key)
) COMMENT='품목 필드 그룹 (섹션)';

샘플 데이터:

INSERT INTO item_field_groups (group_key, group_label, group_description, display_order) VALUES
('basic_info', '기본 정보', '품목의 기본 정보를 입력합니다', 1),
('spec_info', '규격 정보', '품목의 규격 정보를 입력합니다', 2),
('price_info', '가격 정보', '판매가 및 구매가 정보를 입력합니다', 3),
('bom_info', 'BOM 정보', '구성 품목 정보를 입력합니다', 4),
('file_info', '첨부파일', '관련 파일을 첨부합니다', 5);

3. item_field_group_fields (필드-그룹 매핑)

목적: 어떤 필드가 어떤 그룹에 속하는지 정의

CREATE TABLE item_field_group_fields (
  id BIGINT PRIMARY KEY AUTO_INCREMENT,
  field_definition_id BIGINT NOT NULL COMMENT '필드 ID',
  field_group_id BIGINT NOT NULL COMMENT '그룹 ID',
  display_order INT DEFAULT 0 COMMENT '그룹 내 표시 순서',
  is_active BOOLEAN DEFAULT TRUE COMMENT '활성 여부',
  created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
  updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  FOREIGN KEY (field_definition_id) REFERENCES item_field_definitions(id) ON DELETE CASCADE,
  FOREIGN KEY (field_group_id) REFERENCES item_field_groups(id) ON DELETE CASCADE,
  INDEX idx_group (field_group_id),
  INDEX idx_field (field_definition_id)
) COMMENT='필드-그룹 매핑';

샘플 데이터:

-- basic_info 그룹에 itemType, productName, itemName 할당
INSERT INTO item_field_group_fields (field_definition_id, field_group_id, display_order) VALUES
(1, 1, 1),  -- itemType → basic_info
(2, 1, 2),  -- productName → basic_info
(3, 1, 3);  -- itemName → basic_info

4. item_render_rules (조건부 렌더링 규칙)

목적: 특정 필드 값에 따라 다른 필드 표시/숨김

CREATE TABLE item_render_rules (
  id BIGINT PRIMARY KEY AUTO_INCREMENT,
  target_field_id BIGINT NOT NULL COMMENT '적용 대상 필드 ID',
  condition_field_key VARCHAR(50) NOT NULL COMMENT '조건 필드 키 (예: itemType)',
  condition_operator VARCHAR(20) NOT NULL COMMENT '연산자 (==, !=, in, not_in, >, <, >=, <=)',
  condition_value VARCHAR(500) NOT NULL COMMENT '조건 값 (JSON 배열 가능)',
  action VARCHAR(20) NOT NULL COMMENT '액션 (show, hide, required, optional)',
  display_order INT DEFAULT 0 COMMENT '규칙 우선순위',
  is_active BOOLEAN DEFAULT TRUE COMMENT '활성 여부',
  created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
  updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  FOREIGN KEY (target_field_id) REFERENCES item_field_definitions(id) ON DELETE CASCADE,
  INDEX idx_target (target_field_id),
  INDEX idx_condition (condition_field_key)
) COMMENT='조건부 렌더링 규칙';

샘플 데이터:

-- partType 필드는 itemType=PT일 때만 표시
INSERT INTO item_render_rules (target_field_id, condition_field_key, condition_operator, condition_value, action) VALUES
(4, 'itemType', '==', 'PT', 'show'),

-- installationType 필드는 partType=ASSEMBLY AND category1=guide_rail일 때만 표시
-- (복합 조건은 JSON으로 표현 또는 여러 규칙 조합)
(10, 'partType', '==', 'ASSEMBLY', 'show'),
(10, 'category1', '==', 'guide_rail', 'show');

복합 조건 처리 방법:

  • 방법 1: 여러 규칙 생성 (AND 조건)
  • 방법 2: condition_value를 JSON으로 확장
    {
      "operator": "AND",
      "conditions": [
        {"field": "partType", "op": "==", "value": "ASSEMBLY"},
        {"field": "category1", "op": "==", "value": "guide_rail"}
      ]
    }
    

API 설계

GET /api/v1/items/metadata

목적: 품목 유형별 화면 구성 정보 조회

Request:

GET /api/v1/items/metadata?itemType=PT&partType=ASSEMBLY&category1=guide_rail

Query Parameters:

  • itemType (optional): 품목 유형 (FG, PT, RM, SM, CS)
  • partType (optional): 부품 유형 (ASSEMBLY, BENDING, PURCHASED)
  • category1 (optional): 카테고리 (guide_rail, case, etc.)

Response:

{
  "success": true,
  "data": {
    "fields": [
      {
        "key": "itemType",
        "label": "품목유형",
        "type": "select",
        "required": true,
        "options": [
          {"value": "FG", "label": "제품"},
          {"value": "PT", "label": "부품"},
          {"value": "RM", "label": "원자재"},
          {"value": "SM", "label": "부자재"},
          {"value": "CS", "label": "소모품"}
        ],
        "validation": {
          "required": true
        },
        "placeholder": "품목 유형을 선택하세요",
        "helpText": null,
        "displayOrder": 1
      },
      {
        "key": "productName",
        "label": "상품명",
        "type": "text",
        "required": true,
        "options": null,
        "validation": {
          "required": true,
          "min": 2,
          "max": 100
        },
        "placeholder": "상품명을 입력하세요",
        "helpText": "고객에게 표시되는 상품명입니다",
        "displayOrder": 2
      },
      {
        "key": "installationType",
        "label": "설치유형",
        "type": "select",
        "required": true,
        "options": [
          {"value": "wall", "label": "벽면형"},
          {"value": "ceiling", "label": "천정형"}
        ],
        "validation": {
          "required": true
        },
        "displayOrder": 10
      }
    ],
    "groups": [
      {
        "key": "basic_info",
        "label": "기본 정보",
        "description": "품목의 기본 정보를 입력합니다",
        "isCollapsible": false,
        "isCollapsedDefault": false,
        "fields": ["itemType", "productName", "itemName"],
        "displayOrder": 1
      },
      {
        "key": "spec_info",
        "label": "규격 정보",
        "description": "품목의 규격 정보를 입력합니다",
        "isCollapsible": true,
        "isCollapsedDefault": false,
        "fields": ["installationType", "assemblyType", "sideSpecWidth", "assemblyLength"],
        "displayOrder": 2
      }
    ],
    "rules": [
      {
        "targetField": "partType",
        "condition": {
          "field": "itemType",
          "operator": "==",
          "value": "PT"
        },
        "action": "show"
      },
      {
        "targetField": "installationType",
        "condition": {
          "operator": "AND",
          "conditions": [
            {"field": "partType", "operator": "==", "value": "ASSEMBLY"},
            {"field": "category1", "operator": "==", "value": "guide_rail"}
          ]
        },
        "action": "show"
      },
      {
        "targetField": "bendingDiagram",
        "condition": {
          "field": "partType",
          "operator": "==",
          "value": "BENDING"
        },
        "action": "required"
      }
    ]
  }
}

POST /api/v1/items (품목 생성)

기존 API와 동일하지만 동적 필드 처리:

Request:

{
  "itemType": "PT",
  "partType": "ASSEMBLY",
  "category1": "guide_rail",
  "installationType": "wall",
  "assemblyType": "M",
  "sideSpecWidth": 3500,
  "assemblyLength": 5300,
  // ... 동적 필드들
}

Backend 처리:

  1. itemType, partType, category1에 따라 필요한 필드 조회 (metadata)
  2. 동적 필드 검증 (validation_rules 적용)
  3. products 또는 materials 테이블에 저장
  4. 동적 필드는 attributes JSON 컬럼에 저장 (기존 구조 활용)

백엔드-프론트 계약

필드 타입 정의

field_type 프론트 렌더링 검증 예시
text <Input type="text" /> {required, min, max, pattern}
number <Input type="number" /> {required, min, max}
select <Select options={...} /> {required, in:["FG","PT"]}
radio <RadioGroup options={...} /> {required}
checkbox <Checkbox /> {required}
date <Input type="date" /> {required, minDate, maxDate}
textarea <Textarea /> {required, min, max}
file <FileUpload accept={...} /> {required, accept, maxSize}
custom 커스텀 컴포넌트 커스텀 검증

조건부 렌더링 액션

action 의미 프론트 처리
show 필드 표시 display: block
hide 필드 숨김 display: none
required 필수 입력으로 변경 validation.required = true
optional 선택 입력으로 변경 validation.required = false
readonly 읽기 전용 disabled = true
editable 편집 가능 disabled = false

검증 규칙 정의

validation_rules JSON 구조:

{
  "required": true,
  "min": 2,
  "max": 100,
  "pattern": "^[a-zA-Z0-9-]+$",
  "minDate": "2020-01-01",
  "maxDate": "2030-12-31",
  "in": ["FG", "PT", "RM"],
  "notIn": ["deprecated_value"],
  "accept": "image/*",
  "maxSize": 5242880
}

프론트 검증 처리:

// 의사 코드
const validateField = (field, value, validationRules) => {
  if (validationRules.required && !value) {
    return `${field.label}을(를) 입력하세요`;
  }
  if (validationRules.min && value.length < validationRules.min) {
    return `최소 ${validationRules.min}자 이상 입력하세요`;
  }
  // ... 나머지 검증
  return null; // 검증 통과
};

백엔드 검증 처리:

// 의사 코드
$rules = [];
foreach ($fields as $field) {
  $validation = $field->validation_rules;
  if ($validation['required']) {
    $rules[$field->field_key][] = 'required';
  }
  if (isset($validation['min'])) {
    $rules[$field->field_key][] = "min:{$validation['min']}";
  }
  // ... 나머지 검증
}

$request->validate($rules);

조건부 렌더링 처리

프론트 Rule Engine (의사 코드):

const evaluateCondition = (condition, formData) => {
  if (condition.operator === 'AND') {
    return condition.conditions.every(c => evaluateCondition(c, formData));
  }
  if (condition.operator === 'OR') {
    return condition.conditions.some(c => evaluateCondition(c, formData));
  }

  const fieldValue = formData[condition.field];
  switch (condition.operator) {
    case '==':
      return fieldValue === condition.value;
    case '!=':
      return fieldValue !== condition.value;
    case 'in':
      return condition.value.includes(fieldValue);
    // ... 나머지 연산자
  }
};

const isFieldVisible = (field, rules, formData) => {
  const applicableRules = rules.filter(r => r.targetField === field.key);
  return applicableRules.every(rule => {
    if (rule.action === 'show') {
      return evaluateCondition(rule.condition, formData);
    }
    return true;
  });
};

논의 포인트

1. 유연성 범위

Q1: 어디까지 메타데이터로 관리할까?

항목 메타데이터 관리 하드코딩
필드 정의 권장
섹션/그룹 권장
조건부 렌더링 권장
검증 규칙 권장
품목 코드 생성 규칙 논의 필요 복잡한 비즈니스 로직
BOM 계산 로직 복잡한 비즈니스 로직
가격 계산 로직 복잡한 비즈니스 로직

제안:

  • 단순 필드 정의, 검증, 조건부 표시 → 메타데이터
  • 복잡한 비즈니스 로직 (코드 생성, 계산) → 하드코딩 or 별도 룰 엔진

Q2: 모든 품목 유형을 메타데이터로?

  • Option A: FG, PT, RM, SM, CS 모두 메타데이터
  • Option B: 자주 변경되는 PT만 메타데이터, 나머지는 하드코딩
  • 권장: Option A (일관성 + 확장성)

2. 검증 위치

Q3: 검증을 어디서 할까?

위치 장점 단점
프론트 Only 빠른 피드백 보안 취약 (우회 가능)
백엔드 Only 보안 강력 UX 나쁨 (서버 왕복 필요)
프론트 + 백엔드 (권장) UX + 보안 검증 로직 중복

제안:

  • 프론트: metadata의 validation_rules 사용 (UX)
  • 백엔드: 동일한 validation_rules 적용 (보안)
  • 규칙 한 곳(DB)에서 관리 → 양쪽 동일 적용

3. 품목 코드 생성 규칙

Q4: 품목 코드 생성을 어떻게?

현재: 177줄 generateItemCode() 함수 (12개 품목 타입별 로직)

Option A: DB 룰 테이블

CREATE TABLE item_code_generation_rules (
  id BIGINT PRIMARY KEY,
  item_type VARCHAR(10),
  part_type VARCHAR(20),
  category VARCHAR(50),
  code_pattern VARCHAR(200) COMMENT '예: {typeCode}{kindCode}{sizeCode}',
  code_logic JSON COMMENT '생성 로직 (함수명 또는 수식)',
  display_order INT
);
  • 장점: 유연함, 관리자 화면에서 수정 가능
  • 단점: 복잡한 로직 표현 어려움, 보안 위험 (eval 사용 시)

Option B: 백엔드 하드코딩 (현재 방식)

class ItemCodeGenerator {
  public function generate($itemType, $partType, $category, $data) {
    // 12개 분기 로직
  }
}
  • 장점: 복잡한 로직 표현 가능, 안전함
  • 단점: 코드 수정 필요, 배포 필요

Option C: Hybrid (권장)

  • 단순 패턴 (FG, RM, SM, CS): DB 룰 테이블
  • 복잡한 패턴 (PT 12개 카테고리): 백엔드 하드코딩
  • DB에 code_generator_class 필드 → 동적 호출
INSERT INTO item_code_generation_rules VALUES
('FG', NULL, NULL, '{itemName}-{specification}', NULL, 1),
('PT', 'ASSEMBLY', 'guide_rail', NULL, 'GuideRailCodeGenerator::class', 2);

4. 복합 조건 처리

Q5: 복합 조건 (AND, OR, NOT)을 어떻게?

현재 DB 스키마: 단일 조건만 표현

Option A: 여러 규칙 생성 (암시적 AND)

-- installationType은 partType=ASSEMBLY AND category1=guide_rail일 때만
INSERT INTO item_render_rules VALUES
(10, 'partType', '==', 'ASSEMBLY', 'show', 1),
(10, 'category1', '==', 'guide_rail', 'show', 2);
  • 프론트: 같은 targetField의 모든 규칙 AND 처리

Option B: JSON 조건 (명시적 AND/OR)

{
  "operator": "AND",
  "conditions": [
    {"field": "partType", "op": "==", "value": "ASSEMBLY"},
    {"field": "category1", "op": "==", "value": "guide_rail"}
  ]
}

권장: Option B (명확함, 유연성)

5. 데이터 저장 방식

Q6: 동적 필드 값을 어떻게 저장?

현재 구조:

  • products 테이블: 고정 컬럼 (id, itemCode, itemName, etc.)
  • products.attributes JSON: 동적 필드

Option A: 기존 구조 유지

// products.attributes
{
  "installationType": "wall",
  "assemblyType": "M",
  "sideSpecWidth": 3500
}
  • 장점: 기존 코드 호환
  • 단점: JSON 쿼리 성능 (WHERE 조건 사용 어려움)

Option B: EAV 테이블 (완전 동적)

CREATE TABLE item_field_values (
  id BIGINT PRIMARY KEY,
  item_id BIGINT,
  field_definition_id BIGINT,
  field_value TEXT,
  FOREIGN KEY (item_id) REFERENCES products(id),
  FOREIGN KEY (field_definition_id) REFERENCES item_field_definitions(id)
);
  • 장점: 완전 유연, 필드별 쿼리 가능
  • 단점: 조인 많음, 성능 이슈 가능

권장: Option A (Hybrid EAV)

  • 자주 검색하는 필드: 고정 컬럼 (itemCode, itemType, partType)
  • 나머지 동적 필드: JSON (attributes)
  • 필요 시 JSON 인덱스 활용 (MySQL 5.7+)

6. 성능 최적화

Q7: 메타데이터 캐싱?

문제:

  • 매번 metadata API 호출 → DB 쿼리 4개 (fields, groups, mapping, rules)

해결:

  • Backend 캐싱: Redis or File Cache (TTL: 1시간)
    $cacheKey = "item_metadata_{$itemType}_{$partType}_{$category}";
    $metadata = Cache::remember($cacheKey, 3600, function() {
      return $this->buildMetadata();
    });
    
  • Frontend 캐싱: LocalStorage or SessionStorage
    const metadata = localStorage.getItem('metadata_PT_ASSEMBLY_guide_rail');
    if (metadata && !isExpired(metadata)) {
      return JSON.parse(metadata);
    }
    

7. 버전 관리

Q8: 메타데이터 변경 이력 관리?

문제:

  • 필드 정의 변경 시 기존 데이터 호환성?

해결:

  • item_field_definitions.version 컬럼 추가
  • 변경 시 새 버전 생성 (Copy-on-Write)
  • 기존 데이터는 이전 버전 참조

다음 단계

1단계: 프로토타입 개발 (2-3주)

Backend:

  • DB 스키마 생성 (4개 테이블)
  • 샘플 데이터 INSERT (FG, PT-guide_rail)
  • metadata API 구현
  • 캐싱 적용

Frontend:

  • MetaFormBuilder 컴포넌트 개발
  • MetaFieldRenderer 컴포넌트 개발
  • Rule Engine 구현
  • 1개 품목 유형 테스트 (FG or PT-guide_rail)

2단계: 전체 적용 (4-6주)

  • 모든 품목 유형 메타데이터 작성
  • 기존 ItemManagement.tsx 제거
  • 관리자 화면 개발 (필드 관리 UI)
  • 테스트 및 검증

3단계: 최적화 및 확장 (2-3주)

  • 성능 테스트 및 최적화
  • 품목 코드 생성 규칙 통합
  • BOM 편집기 메타데이터 적용
  • 문서화

참고 문서

  1. METADATA_DRIVEN_UI_DETAILED_ANALYSIS.md - 상세 버전 (현재 분석 + DB 스키마 상세 + 구현 예시)
  2. PHASE_0_FINAL_REPORT.md - Phase 0 종합 보고서
  3. react_code_analysis_summary.md - React 코드 완전 분석
  4. api_gap_validation_report.md - Gap 검증 보고서

작성자: 백엔드 개발자 검토 요청: 프론트엔드 개발자 논의 일정: TBD 업데이트: 2025-11-12