Files
vexplor/docs/COMPONENT_MANAGEMENT_REFACTORING_PROPOSAL.md
DDD1542 192b678bce fix: 화면 복제 기능 개선 및 관련 버그 수정
- 화면 복제 기능을 개선하여 DB 구조 개편 후의 효율적인 화면 관리를 지원합니다.
- 그룹 복제 시 버튼의 `targetScreenId`가 새 화면으로 매핑되지 않는 버그를 수정하였습니다.
- 관련된 서비스 및 쿼리에서 `table_type_columns`를 사용하여 라벨 정보를 조회하도록 변경하였습니다.
- 여러 컨트롤러 및 서비스에서 `column_labels` 대신 `table_type_columns`를 참조하도록 업데이트하였습니다.
2026-01-28 11:24:25 +09:00

20 KiB
Raw Blame History

컴포넌트 관리 시스템 리팩토링 제안서

1. 현재 문제점

1.1 핵심 문제

컴포넌트 오류 발생 시 → 코드 수정 → 해당 컴포넌트 사용하는 모든 화면에 영향

현재 구조에서는:

  • 컴포넌트 코드가 프론트엔드에 하드코딩되어 있음
  • 설정이 JSONB로 각 화면마다 중복 저장
  • 컴포넌트 수정 시 개별 화면 데이터 마이그레이션 필요

1.2 구체적 문제 사례

예: v2-table-list 컴포넌트의 pagination 구조 변경 시

현재 방식:
1. 프론트엔드 코드 수정
2. screen_layouts 테이블의 모든 해당 컴포넌트 JSON 수정 필요
3. 100개 화면에서 사용 중이면 100개 레코드 마이그레이션
4. 테스트 및 검증 공수 발생

2. 개선 방안 비교

방안 1: URL 기반 코드 참조 + 설정 분리

개념

┌─────────────────────────────────────────────────────────────┐
│                    컴포넌트 코드 (URL 참조)                    │
├─────────────────────────────────────────────────────────────┤
│  경로: /lib/registry/components/v2-table-list/              │
│  - 상대경로: ./v2-table-list                                 │
│  - 절대경로: @/lib/registry/components/v2-table-list        │
└─────────────────────────────────────────────────────────────┘
                              │
                              ▼
┌─────────────────────────────────────────────────────────────┐
│                      설정 분리 저장                           │
├────────────────────────┬────────────────────────────────────┤
│    공용 설정 (1개)      │      회사별 설정 (N개)              │
│                        │                                    │
│  - 기본 pagination     │  - A회사: pageSize=20             │
│  - 기본 toolbar        │  - B회사: pageSize=50             │
│  - 기본 columns 구조   │  - C회사: 특수 컬럼 추가           │
└────────────────────────┴────────────────────────────────────┘

데이터베이스 구조 (예시)

-- 1. 컴포넌트 정의 테이블 (공용)
CREATE TABLE component_definitions (
  component_id VARCHAR(50) PRIMARY KEY,  -- 'v2-table-list'
  component_path VARCHAR(200) NOT NULL,   -- '@/lib/registry/components/v2-table-list'
  component_name VARCHAR(100),            -- '테이블 리스트'
  category VARCHAR(50),                   -- 'display'
  version VARCHAR(20),                    -- '2.1.0'
  default_config JSONB,                   -- 기본 설정 (공용)
  is_active CHAR(1) DEFAULT 'Y'
);

-- 2. 회사별 컴포넌트 설정 오버라이드
CREATE TABLE company_component_config (
  id SERIAL PRIMARY KEY,
  company_code VARCHAR(50) NOT NULL,
  component_id VARCHAR(50) REFERENCES component_definitions(component_id),
  config_override JSONB,                  -- 회사별 오버라이드 설정
  UNIQUE(company_code, component_id)
);

-- 3. 화면 레이아웃 (간소화)
CREATE TABLE screen_layouts (
  layout_id SERIAL PRIMARY KEY,
  screen_id INTEGER,
  component_id VARCHAR(50) REFERENCES component_definitions(component_id),
  position_x INTEGER,
  position_y INTEGER,
  width INTEGER,
  height INTEGER,
  instance_config JSONB                   -- 해당 인스턴스만의 설정 (최소화)
);

설정 병합 로직

// 설정 우선순위: 인스턴스 설정 > 회사 설정 > 공용 기본 설정
function getComponentConfig(componentId: string, companyCode: string, instanceConfig: any) {
  const defaultConfig = await getDefaultConfig(componentId);           // 공용
  const companyConfig = await getCompanyConfig(componentId, companyCode); // 회사별
  
  return deepMerge(defaultConfig, companyConfig, instanceConfig);
}

장점

장점 설명
코드 단일 관리 컴포넌트 코드는 한 곳에서만 관리 (URL 참조)
설정 계층화 공용 → 회사 → 인스턴스 순으로 설정 상속
유연한 커스터마이징 회사별로 다른 기본값 설정 가능
마이그레이션 최소화 공용 설정 변경 시 한 곳만 수정
버전 관리 컴포넌트 버전별 호환성 관리 가능

단점

단점 설명
복잡한 병합 로직 3단계 설정 병합 로직 필요
런타임 오버헤드 설정 조회 시 여러 테이블 JOIN
디버깅 어려움 최종 설정이 어디서 온 것인지 추적 필요
기존 데이터 마이그레이션 기존 JSONB 데이터를 분리 저장 필요

방안 2: 정형화된 테이블 (컬럼 파싱)

개념

┌─────────────────────────────────────────────────────────────┐
│               컴포넌트별 전용 테이블 생성                      │
└─────────────────────────────────────────────────────────────┘
                              │
        ┌─────────────────────┼─────────────────────┐
        ▼                     ▼                     ▼
┌───────────────┐    ┌───────────────┐    ┌───────────────┐
│ table_list    │    │ button_config │    │ split_panel   │
│ _components   │    │ _components   │    │ _components   │
├───────────────┤    ├───────────────┤    ├───────────────┤
│ id            │    │ id            │    │ id            │
│ screen_id     │    │ screen_id     │    │ screen_id     │
│ table_name    │    │ action_type   │    │ left_table    │
│ page_size     │    │ target_screen │    │ right_table   │
│ show_checkbox │    │ button_text   │    │ split_ratio   │
│ show_excel    │    │ icon          │    │ transfer_type │
│ ...           │    │ ...           │    │ ...           │
└───────────────┘    └───────────────┘    └───────────────┘

데이터베이스 구조 (예시)

-- 1. 공통 컴포넌트 메타 테이블
CREATE TABLE component_instances (
  instance_id SERIAL PRIMARY KEY,
  screen_id INTEGER NOT NULL,
  component_type VARCHAR(50) NOT NULL,  -- 'table-list', 'button', 'split-panel'
  position_x INTEGER,
  position_y INTEGER,
  width INTEGER,
  height INTEGER,
  company_code VARCHAR(50)
);

-- 2. 테이블 리스트 컴포넌트 전용 테이블
CREATE TABLE component_table_list (
  id SERIAL PRIMARY KEY,
  instance_id INTEGER REFERENCES component_instances(instance_id),
  table_name VARCHAR(100),
  page_size INTEGER DEFAULT 20,
  show_checkbox BOOLEAN DEFAULT true,
  checkbox_multiple BOOLEAN DEFAULT true,
  show_excel BOOLEAN DEFAULT true,
  show_refresh BOOLEAN DEFAULT true,
  show_search BOOLEAN DEFAULT true,
  header_style VARCHAR(20) DEFAULT 'default',
  row_height VARCHAR(20) DEFAULT 'normal',
  auto_load BOOLEAN DEFAULT true
);

-- 3. 테이블 리스트 컬럼 설정 테이블
CREATE TABLE component_table_list_columns (
  id SERIAL PRIMARY KEY,
  table_list_id INTEGER REFERENCES component_table_list(id),
  column_name VARCHAR(100) NOT NULL,
  display_name VARCHAR(100),
  visible BOOLEAN DEFAULT true,
  sortable BOOLEAN DEFAULT true,
  searchable BOOLEAN DEFAULT false,
  width INTEGER,
  align VARCHAR(10) DEFAULT 'left',
  format VARCHAR(20) DEFAULT 'text',
  display_order INTEGER DEFAULT 0,
  fixed VARCHAR(10),  -- 'left', 'right', null
  editable BOOLEAN DEFAULT true
);

-- 4. 버튼 컴포넌트 전용 테이블
CREATE TABLE component_button (
  id SERIAL PRIMARY KEY,
  instance_id INTEGER REFERENCES component_instances(instance_id),
  button_text VARCHAR(100),
  action_type VARCHAR(50),        -- 'save', 'delete', 'navigate', 'popup'
  target_screen_id INTEGER,
  target_url VARCHAR(500),
  numbering_rule_id VARCHAR(100),
  variant VARCHAR(20) DEFAULT 'default',
  size VARCHAR(10) DEFAULT 'md',
  icon VARCHAR(50)
);

-- 5. 분할 패널 컴포넌트 전용 테이블
CREATE TABLE component_split_panel (
  id SERIAL PRIMARY KEY,
  instance_id INTEGER REFERENCES component_instances(instance_id),
  left_table_name VARCHAR(100),
  right_table_name VARCHAR(100),
  split_ratio INTEGER DEFAULT 50,
  transfer_enabled BOOLEAN DEFAULT true,
  transfer_button_label VARCHAR(100)
);

장점

장점 설명
타입 안정성 각 컬럼이 명확한 데이터 타입
SQL 쿼리 용이 WHERE page_size > 50 같은 직접 쿼리 가능
인덱스 최적화 특정 컬럼에 인덱스 생성 가능
데이터 무결성 외래키, CHECK 제약 조건 적용 가능
일괄 수정 용이 UPDATE component_table_list SET page_size = 30 WHERE ...
명확한 스키마 어떤 설정이 있는지 테이블 구조로 명확히 파악

단점

단점 설명
테이블 폭발 70+ 컴포넌트 × 하위 설정 = 100개 이상 테이블
스키마 변경 필수 새 설정 추가 시 ALTER TABLE 필요
JOIN 복잡도 화면 로드 시 여러 테이블 JOIN
유연성 저하 임시/실험적 설정 저장 어려움
마이그레이션 대규모 기존 JSONB → 정형 테이블 대규모 작업

3. 상세 비교 분석

3.1 개발 공수 비교

항목 방안 1 (URL + 설정 분리) 방안 2 (정형 테이블)
초기 설계 중간 높음 (테이블 설계)
마이그레이션 중간 매우 높음
프론트엔드 수정 중간 높음 (쿼리 변경)
백엔드 수정 중간 높음 (ORM/쿼리)
테스트 중간 높음

3.2 유지보수 비교

항목 방안 1 방안 2
컴포넌트 버그 수정 쉬움 (코드만) 쉬움 (코드만)
새 설정 추가 쉬움 (JSON 확장) 어려움 (ALTER TABLE)
일괄 설정 변경 중간 (JSON 쿼리) 쉬움 (SQL UPDATE)
디버깅 중간 쉬움 (명확한 컬럼)

3.3 성능 비교

항목 방안 1 방안 2
읽기 성능 중간 (설정 병합) 좋음 (직접 조회)
쓰기 성능 좋음 (단일 JSONB) 중간 (여러 테이블)
검색 성능 나쁨 (JSONB 검색) 좋음 (인덱스)
캐싱 좋음 (계층 캐싱) 중간

4. 하이브리드 방안 제안

두 방안의 장점을 결합한 하이브리드 접근법:

4.1 구조

┌─────────────────────────────────────────────────────────────┐
│                  컴포넌트 메타 (정형 테이블)                   │
├─────────────────────────────────────────────────────────────┤
│  component_id | path | name | category | version            │
└─────────────────────────────────────────────────────────────┘
                              │
                              ▼
┌─────────────────────────────────────────────────────────────┐
│              설정 계층 (공용 → 회사 → 인스턴스)                │
├────────────────────────┬────────────────────────────────────┤
│  공용 기본 설정 (JSONB) │  회사별 오버라이드 (JSONB)          │
└────────────────────────┴────────────────────────────────────┘
                              │
                              ▼
┌─────────────────────────────────────────────────────────────┐
│             핵심 설정만 정형 컬럼 (자주 검색/수정)             │
├─────────────────────────────────────────────────────────────┤
│  table_name | page_size | is_active | ...                   │
│  + extra_config JSONB (나머지 설정)                          │
└─────────────────────────────────────────────────────────────┘

4.2 데이터베이스 구조

-- 1. 컴포넌트 정의 (공용)
CREATE TABLE component_definitions (
  component_id VARCHAR(50) PRIMARY KEY,
  component_path VARCHAR(200) NOT NULL,
  component_name VARCHAR(100),
  category VARCHAR(50),
  version VARCHAR(20),
  default_config JSONB,               -- 기본 설정
  schema_version INTEGER DEFAULT 1,   -- 설정 스키마 버전
  is_active CHAR(1) DEFAULT 'Y'
);

-- 2. 컴포넌트 인스턴스 (핵심 필드 정형화 + 나머지 JSONB)
CREATE TABLE component_instances (
  instance_id SERIAL PRIMARY KEY,
  screen_id INTEGER NOT NULL,
  company_code VARCHAR(50) NOT NULL,
  component_id VARCHAR(50) REFERENCES component_definitions(component_id),
  
  -- 공통 정형 필드 (자주 검색/수정)
  position_x INTEGER,
  position_y INTEGER,
  width INTEGER,
  height INTEGER,
  is_visible BOOLEAN DEFAULT true,
  display_order INTEGER DEFAULT 0,
  
  -- 컴포넌트 타입별 핵심 필드 (자주 검색/수정)
  target_table VARCHAR(100),          -- table-list, split-panel 등
  action_type VARCHAR(50),            -- button
  
  -- 나머지 상세 설정 (유연성)
  config_override JSONB,              -- 인스턴스별 설정 오버라이드
  
  created_at TIMESTAMP DEFAULT NOW(),
  updated_at TIMESTAMP DEFAULT NOW()
);

-- 3. 회사별 컴포넌트 기본 설정
CREATE TABLE company_component_defaults (
  id SERIAL PRIMARY KEY,
  company_code VARCHAR(50) NOT NULL,
  component_id VARCHAR(50) REFERENCES component_definitions(component_id),
  config_override JSONB,              -- 회사별 기본값 오버라이드
  UNIQUE(company_code, component_id)
);

-- 인덱스 최적화
CREATE INDEX idx_instances_screen ON component_instances(screen_id);
CREATE INDEX idx_instances_company ON component_instances(company_code);
CREATE INDEX idx_instances_component ON component_instances(component_id);
CREATE INDEX idx_instances_target_table ON component_instances(target_table);

4.3 설정 조회 로직

async function getComponentFullConfig(
  instanceId: number,
  companyCode: string
): Promise<ComponentConfig> {
  // 1. 인스턴스 + 컴포넌트 정의 조회 (단일 쿼리)
  const result = await query(`
    SELECT 
      i.*,
      d.default_config,
      c.config_override as company_override
    FROM component_instances i
    JOIN component_definitions d ON i.component_id = d.component_id
    LEFT JOIN company_component_defaults c 
      ON c.component_id = i.component_id 
      AND c.company_code = i.company_code
    WHERE i.instance_id = $1
  `, [instanceId]);

  // 2. 설정 병합 (공용 → 회사 → 인스턴스)
  return deepMerge(
    result.default_config,      // 공용 기본값
    result.company_override,    // 회사별 오버라이드
    result.config_override      // 인스턴스별 오버라이드
  );
}

4.4 일괄 수정 예시

-- 특정 테이블을 사용하는 모든 컴포넌트의 page_size 변경
UPDATE component_instances
SET config_override = jsonb_set(
  COALESCE(config_override, '{}'),
  '{pagination,pageSize}',
  '30'
)
WHERE target_table = 'user_info';

-- 특정 회사의 모든 테이블 리스트 기본값 변경
UPDATE company_component_defaults
SET config_override = jsonb_set(
  COALESCE(config_override, '{}'),
  '{pagination,pageSize}',
  '50'
)
WHERE company_code = 'COMPANY_A' 
  AND component_id = 'v2-table-list';

5. 권장사항

5.1 단기 (1-2주)

방안 1 (URL + 설정 분리) 권장

이유:

  • 현재 JSONB 구조와 호환성 유지
  • 마이그레이션 공수 최소화
  • 점진적 적용 가능

5.2 장기 (1-2개월)

하이브리드 방안 권장

이유:

  • 자주 검색/수정되는 핵심 필드만 정형화
  • 나머지는 JSONB로 유연성 유지
  • 성능과 유연성의 균형

6. 마이그레이션 로드맵

Phase 1: 컴포넌트 정의 분리 (1주)

-- 기존 컴포넌트를 component_definitions로 추출
INSERT INTO component_definitions (component_id, component_path, default_config)
SELECT DISTINCT 
  componentType,
  CONCAT('@/lib/registry/components/', componentType),
  '{}' -- 기본값은 코드에서 정의
FROM (
  SELECT properties->>'componentType' as componentType
  FROM screen_layouts
  WHERE properties->>'componentType' IS NOT NULL
) t;

Phase 2: 회사별 설정 분리 (1주)

// 각 회사별 공통 패턴 분석 후 company_component_defaults 생성
async function extractCompanyDefaults(companyCode: string) {
  // 해당 회사의 컴포넌트 사용 패턴 분석
  // 가장 많이 사용되는 설정을 기본값으로 추출
}

Phase 3: 인스턴스 설정 최소화 (2주)

// 인스턴스별 설정에서 기본값과 동일한 부분 제거
async function minimizeInstanceConfig(instanceId: number) {
  const fullConfig = currentConfig;
  const defaultConfig = getDefaultConfig();
  const companyConfig = getCompanyConfig();
  
  // 차이나는 부분만 저장
  const minimalConfig = getDiff(fullConfig, merge(defaultConfig, companyConfig));
  await saveInstanceConfig(instanceId, minimalConfig);
}

7. 결론

방안 적합한 상황
방안 1 (URL + 설정 분리) 빠른 개선이 필요하고, 현재 구조와의 호환성 중요 시
방안 2 (정형 테이블) 완전한 재설계가 가능하고, 장기적 유지보수 최우선 시
하이브리드 두 방안의 장점을 모두 원하고, 충분한 개발 리소스 있을 시

권장: 단기적으로 방안 1을 적용하고, 안정화 후 하이브리드로 전환