목표
상품 3중 관리 제거
DB 융합이 아니라 마스터 데이터(MDM) 문제. 한 곳에서 작성, 나머지는 소비.
같은 상품을 Foodil·Quoteapp에 중복 관리해 생기는 드리프트를 없앱니다. 신규 상품 마스터 어드민을 단일 진실로 두고, Foodil·Quoteapp은 단방향 동기화로 투영(projection)을 받아 “읽기 위주”로 소비합니다. CIPL은 문서 생성 서비스로 그대로 둡니다. DB를 합치는 것이 아니라, 상품을 한 번만 관리하는 구조입니다.
진짜 문제는 “같은 상품이 여러 곳에서 관리돼 어긋난다”입니다. 이는 마스터 데이터(MDM) 문제이지, 세 DB를 하나로 융합하는 문제가 아닙니다. 그래서 회사 카탈로그의 단일 작성 지점(상품 마스터 어드민)을 두고, Foodil·Quoteapp은 그 투영을 받습니다. 각 앱의 DB·인증·주문/견적은 그대로 두고, 상품 데이터만 마스터로 모읍니다.
색으로 시스템을 구분합니다. 새 구조에서 누가 소비자(투영)이고 누가 문서 서비스인지를 함께 표시했습니다.
B2B 마켓플레이스. 회사 상품은 마스터 투영을 받고, 셀러 상품은 지금처럼 Foodil 자체 관리. 주문·스토어·가격정책·임베딩·RLS는 그대로 로컬.
소비자 (투영)내부 견적/PO ERP. 지금은 JSON 파일이 DB 역할. 정식 Postgres로 전환하고 카탈로그 전체를 마스터 투영으로 받음. 견적·PO·원장은 로컬.
소비자 (투영 · 신규 DB)상업송장/포장명세서 도구. 회사 상품은 Quoteapp export로 문서 생성 시점에만 전달됨. 외부 고객의 비공개 카탈로그는 범위 밖.
문서 서비스 (동기화 X)핵심 범위: “3중 관리”의 실체는 회사 카탈로그가 Foodil과 Quoteapp에 중복된다는 것입니다. 마스터는 회사 상품만 다스리고, Foodil의 다른 셀러 상품은 그대로 둡니다. CIPL은 이미 Quoteapp이 문서 시점에 데이터를 밀어주므로 별도 동기화 대상이 아닙니다.
이 선택은 기술 취향이 아니라 전략 문제입니다. 두 가지가 답을 결정합니다 — (1) 세 앱이 하나의 플랫폼으로 수렴하는가, (2) 드리프트가 실제로 얼마나 큰가(공유 상품 수 × 변경 빈도 × 오류 비용). 아래는 두 극단(전체 통합 · 분리 유지)과 그 사이의 MDM을 정직하게 비교한 것입니다.
세 앱을 하나의 DB로 합치고 모두 그 DB로 재연결.
전략적 베팅신규 상품 마스터를 단일 작성 지점으로, 앱은 단방향 동기화로 투영 소비.
권장아무것도 통합하지 않고 지금처럼 각자 관리.
최소 · 조건부| 방안 | 비용·노력 | 리스크 | 드리프트 해소 | 적합한 경우 |
|---|---|---|---|---|
| A · 전체 통합 | 높음 | 높음 (회사 간 누출) | 완전 | 하나의 플랫폼으로 수렴 결정 시 |
| B · MDM 허브앤스포크 권장 | 중간 | 낮음 (격리) | 완전 (점진) | 구분된 앱 + 실재하는 드리프트 |
| C · 분리 유지 | 0 (+ Quoteapp DB) | 중간 (오류 전파) | 없음 | 드리프트 미미 + 전략적 분기 |
초기엔 “단일 DB 통합”을 검토했으나, 목표가 “상품 3중 관리 제거”(마스터 데이터 문제)로 확정되면서 더 안전하고 점진적인 허브앤스포크 MDM으로 전환했습니다.
상품 3중 관리 제거
DB 융합이 아니라 마스터 데이터(MDM) 문제. 한 곳에서 작성, 나머지는 소비.
허브앤스포크
신규 상품 마스터 어드민 + core DB = 단일 작성 지점(허브). 소비자는 스포크.
Foodil·Quoteapp 투영
단방향 동기화로 투영 수신. 각자 DB·인증·주문/견적은 그대로 유지.
문서 서비스
Quoteapp export로 문서 생성. 동기화 대상 아님. 외부 고객 카탈로그 제외.
회사 카탈로그만
Foodil 셀러 상품은 그대로. master-fed 행만 master_product_id로 표시.
DB-native (이벤트 없음)
CIPL live read · Foodil logical replication+overlay · Quoteapp 직접. 자체 outbox/pg_net 파이프라인 없음.
마스터만 작성
소비자는 master-fed 행의 master 필드 읽기전용. 이게 “한 번만 관리”의 핵심.
일회성 정합
Foodil+Quoteapp 카탈로그를 1회 병합(중복 제거) 후 동기화 가동.
Quoteapp 먼저 (thin slice)
DB 없는 내부 도구라 위험 최소. 검증 후 Foodil 소비자 연결. 빅뱅 없음.
One master, two projections, one doc service.
상품의 작성/편집은 마스터 어드민 한 곳에서만 일어납니다. Foodil과 Quoteapp은 마스터에서 단방향 동기화로 투영을 받아 “읽기 위주”로 소비합니다. 각 앱은 자기 DB·인증·RLS·주문/견적을 그대로 유지합니다.
마스터는 회사 카탈로그만 다스립니다. Foodil의 다른 셀러 상품은 영향이 없고, 마스터에서 온 행만 master_product_id로 표시되어 마스터 소유 필드가 읽기전용이 됩니다. 그래서 “편집기 회수”는 “Foodil 상품 UI 동결”이 아니라 “master-fed 행의 master 필드만 잠금”입니다.
배포는 DB 네이티브로 합니다 — 자체 이벤트 파이프라인(outbox/pg_net)을 만들지 않습니다. CIPL은 문서 시점 live read(항상 최신), Foodil은 logical replication으로 읽기전용 사실 미러를 받아 로컬 overlay(공개여부·스토어·가격정책·rich description)와 JOIN, Quoteapp 어드민은 core를 직접 read/write. 소유는 컬럼이 아니라 테이블 단위로 나눠 로컬 데이터 덮어쓰기 사고를 막습니다.
상품은 마스터 어드민에서만 작성/편집됩니다(허브). Foodil·Quoteapp은 단방향 동기화로 투영을 받습니다(스포크). CIPL은 Quoteapp export로 문서 생성 시점에만 데이터를 받는 다운스트림 서비스입니다.
소비자마다 실제 필요에 맞는 가장 단순·견고한 방식으로 배포합니다 — CIPL은 문서 시점 live read(항상 최신), Foodil은 logical replication으로 읽기전용 사실 미러 + 로컬 overlay JOIN, Quoteapp(어드민)은 core를 직접 read/write. outbox·pg_net 같은 자체 이벤트 파이프라인을 만들지 않습니다.
먼저 회사 카탈로그를 1회 정합(Foodil + Quoteapp 중복 제거)해 마스터를 채우고, master_product_id를 역기록한 뒤 동기화를 켭니다. 이후 작성은 어드민에서만. 롤아웃은 Quoteapp 먼저(thin slice) → Foodil 순으로 점진 확장합니다.
일회성 정합 시 각 시스템의 필드가 마스터의 어디로 가는지입니다. 검토에서 검증된 충돌(단위 vs 카톤 중량, 통화 부재, 바코드 유형, 규격 원문)은 모두 해소했습니다. 동기화 시 마스터 소유 필드만 소비자로 갱신됩니다.
| 개념 | Foodil | Quoteapp | 마스터(core) | 소유 |
|---|---|---|---|---|
| 상품 ID | product_id bigint | id 'prod-…' | product_id uuid + sources | 마스터 |
| 품번/SKU | — | itemNo | item_no | 마스터 |
| 브랜드 | brand_id (필수) | brand 문자열 | brand_id + brand_text | 마스터 |
| 상품명 | name | product (805건 공란→격리) | name (nullable) | 마스터 |
| 규격 원문 | content | spec | spec_raw (원문 보존) | 마스터 |
| 단위/카톤 중량 | net_weight·gross | netWeightKg·grossWeightKg | net_weight_per_unit · *_carton_kg (분리) | 마스터 |
| CBM·카톤크기 | carton_*_mm·cbm | cartonSize 문자열 | carton_*_mm·cbm (파싱) | 마스터 |
| 바코드 | barcode (단일) | pcs/bundle/carton | barcode_piece/bundle/carton | 마스터 |
| HS code | hs_code | hsCode | hs_code (nullable) | 마스터 |
| 원가·기준가 | prices 통화 없음 | KRW/USD 원가·판매가 | product_prices 통화+kind 명시 | 마스터 |
| 공개여부·스토어 | is_public · 스토어 | 없음 | (마스터에 두지 않음) | Foodil 로컬 |
| 마켓 가격정책 | 커미션·바이어별 | — | (마스터에 두지 않음) | Foodil 로컬 |
| 견적 라인·원장 | — | quotes.items·ledger | (마스터에 두지 않음) | Quoteapp 로컬 |
한 번에 전부 만들지 않습니다. 검증(P0)으로 시작해 마스터+thin slice(P1)로 루프를 증명하고, Foodil 소비자(P2), 전체 확장+강화(P3)를 거쳐 정착·거버넌스(P4)에서 “3중 관리”를 완전히 종료합니다. 각 단계는 완료 기준을 통과해야 다음으로 넘어갑니다.
왼쪽에서 오른쪽으로 진행합니다. 앞 단계의 완료 기준을 통과해야 다음 단계로 넘어가며, 최종 목표는 한 번만 관리(드리프트 0)입니다.
빌드 전 가정을 실제 앱 코드로 검증하고 열린 결정을 확정한다. 이 패키지엔 앱 소스가 없어 실제 레포 확인이 필요한 단계.
소량으로 작성→동기화→소비 루프를 증명한다. Quoteapp은 DB가 없어 위험이 가장 낮고 효과가 즉시 보이는 첫 대상.
Foodil 회사 상품을 마스터 투영으로 전환하고 중복 편집을 차단한다. 셀러 상품은 그대로.
thin slice를 전체 카탈로그로 확장하고 운영을 견고화한다.
“3중 관리”를 완전히 종료하고 지속 운영 체계를 둔다. 단일 작성 지점이 정착되는 단계.
설계 스펙 작성 완료(MDM-DESIGN.md). 현재 위치는 Phase 0(검증) — 전체 일정은 위 로드맵 참조.
확인 필요 (열린 항목)