이커머스에서 *진짜 어려운 건 코드가 아니다* — 재고 drift·쿠폰 부담 책임·정산 회계·환불 7일·셀러 분쟁 같은 *비기술* 문제 7가지의 *기술 설계 침투* 패턴
이커머스 시스템을 기술적으로 완벽하게 만들어 놓고도 — 결제가 P99 100ms 에 들어오고, 정산이 5분 안에 끝나고, ArchUnit 으로 헥사고날 경계까지 강제했는데도 — 서비스가 안 굴러가는 순간들 이 있다. 그건 코드 버그가 아니다. 재고 숫자가 맞지 않고, 쿠폰을 누가 부담할지 합의가 없고, 세금계산서를 언제 발행하는지 회계팀이 모르고, 환불 거부에 소비자원이 들어오고, 셀러가 가격을 비합리적으로 올리는데도 막을 권한이 없는 — 코드 한 줄로는 절대 풀 수 없는 문제들이다.
이 글은 이커머스 도메인에서 반복적으로 마주치는 비기술 문제 7 가지 를 정리하고, 그 비기술 문제가 어떻게 *기술 설계 에 침투해서 시스템 구조를 바꿔놓는지* 그 패턴을 본다. (1) 재고 drift, (2) 쿠폰·프로모션 의 조합 폭발 + 부담 책임, (3) 정산의 회계·세무 복잡도, (4) 환불·취소의 법정 7일 룰 과 회계 처리, (5) 외부 의존 (PG / 카드사 / 3PL), (6) 이해관계자 충돌, (7) 어뷰징 / 사기 / 분쟁 — 그리고 마지막으로 그 모든 것이 기술 설계 에 어떻게 침투하는가.
TL;DR
“이커머스의 본질은 기술이 아니라 약속의 다층 정합성이다” — 셀러와의 약속, 구매자와의 약속, 카드사·PG 와의 약속, 국세청·소비자보호법과의 약속, 3PL 과의 약속. 이 약속들이 서로 충돌 한다. 코드는 그 충돌을 해결하지 못한다 — 다만 *기록 할 뿐이다. 그래서 이커머스 시스템의 진짜 설계 능력은 *충돌을 *명시화 하고 책임 layer 를 *분리 하고 되돌릴 수 있는 상태 로 기록 하는 것*.
이 글에서 다룰 7 가지 비기술 문제 가 결국 기술 설계 에 침투하는 4 가지 공통 패턴:
| 비기술 문제 | 기술 설계 침투 |
|---|---|
| 결정권자가 여럿이고 합의 비용이 큼 | 상태 머신 + 감사 로그 + 명시 권한 으로 누가 언제 무엇을 결정했는지 영구 기록 |
| 외부 시스템·법·인간 행동 변경 빈도 높음 | adapter 분리 + 정책 외부화 로 코어 비즈니스 로직 보호 |
| 사고 발생 후 책임 분담 의 모호함 | 추적 가능 사슬 (orderId → paymentId → settlementId) 으로 감사 가능성 보장 |
| 새 사고 의 예측 불가능성 | 소프트 삭제 + 이벤트 소싱 친화 + 멱등 재시도 로 되돌리기 가능한 상태 유지 |
“이커머스의 진짜 어려움은 *법무·회계·세무·분쟁 부서가 기술팀에게 묻는 질문에 답할 수 있어야 한다 는 것”* — “7 일 전에 그 주문 어떤 쿠폰 어떤 금액으로 결제됐고 누가 환불 처리했고 정산서에 반영됐나” — 이 한 질문에 답하지 못하면 기술 시스템이 비즈니스 무게를 못 견디는 것.
0. 들어가며 — 왜 비기술 문제가 더 어렵나
기술적 버그 는 재현 가능 하다. 로그 가 남는다. 고치면 끝 난다. 그런데 비기술 문제 는:
- 재현 불가능 — 같은 셀러가 같은 상품에 같은 행동을 반복하지 않는다
- 로그가 부족 — 전화 통화 / 카톡 / 회의실 결정 의 흔적이 코드에 안 남는다
- 고쳐도 끝나지 않음 — 다음 달에 비슷한 사고가 다른 셀러에게서 다른 모양 으로 다시 발생
이 비대칭이 본질이다. 기술 문제는 해결 의 문제고, 비기술 문제는 관리 의 문제다. 우리가 코드를 잘 짜는 능력으로 비기술 문제를 해결 하려고 들면 과도 설계 (모든 시나리오에 자동화 코드) 가 되거나 과소 설계 (사람이 모든 걸 책임지는 단순 CRUD) 가 된다. 정공은 그 사이 — 어디까지가 코드의 영역이고 어디부터가 사람의 영역인지 *경계를 명시 하는 것*.
1. 재고 drift — 실 재고와 시스템 재고는 절대 같지 않다
1-1. 본질적 비대칭
시스템 재고 는 DB 의 숫자. 실 재고 는 창고의 박스. 둘이 같은 적은 없다. 시스템 재고가 100 인데:
- 입고 과정에서 13 개 파손 — 검수 안 했음 → 시스템 100, 실 87
- 셀러가 다른 채널에서 30 개 팜 — 동기화 안 함 → 시스템 100, 실 70
- 상자 라벨 잘못 붙음 — 다른 SKU 로 검수됨 → A 시스템 100, A 실 80, B 시스템 50, B 실 70
- 분실 / 도난 — 누군가가 가져감 → 시스템 100, 실 90
- 유통기한 만료 — 폐기 안 함 → 팔리지만 받는 사람은 환불 요청
이 차이는 코드로 풀리지 않는다. 물리 세계의 검수 프로세스 만이 푼다.
1-2. 기술 설계 침투 — 재고 ≠ 단일 숫자
순진한 모델은:
products.stock INTEGER -- 100, 99, 98, ...
이걸로는 재고 drift 의 *원인 추적 이 불가능. 누가 언제 무엇을 했는지 흔적이 없다. 정공은 *재고를 시계열 이벤트의 적분 으로 모델링.
inventory_events
id, sku_id, delta, reason, ref_type, ref_id, occurred_at, recorded_by
sku_id | delta | reason | ref_type | ref_id
-----------+-------+----------------+--------------+--------
SKU-A | +100 | INITIAL_STOCK | NULL | NULL
SKU-A | -3 | ORDER_DEDUCT | order | O-001
SKU-A | -13 | DAMAGE | manual | M-002
SKU-A | +50 | RESTOCK | inbound | I-005
SKU-A | -5 | INVENTORY_AUDIT| stocktake | S-006
현재 재고 = SUM(delta). 그러나 더 중요한 건 각 변경의 출처 추적. 셀러가 등록한 +100 인지, MD 가 손으로 줄인 -13 인지, 주문 차감 -3 인지. 책임 사슬 이 코드에 명시되어 있어야 분쟁 시 답변 가능.
추가로 논리 재고 (시스템이 팔 수 있다고 약속 한 수) 와 물리 재고 (창고에 실제로 있는 수) 를 분리 한다:
sku_a.physical_stock = 100 -- 창고 직원이 본 숫자
sku_a.reserved_stock = 12 -- 결제 진행 중이라 잠금
sku_a.committed_stock = 5 -- 결제 완료, 배송 대기
sku_a.available_stock = physical_stock - reserved_stock - committed_stock = 83
available_stock 만이 주문 가능 수. 이 모델은 checkout 동시성 + 재고 drift 둘 다 풀어준다.
1-3. 핵심 패턴
| 비기술 문제 | 코드 패턴 |
|---|---|
| 재고 drift 의 원인이 사람·물리 | inventory_events 로 모든 변경에 reason / actor / ref 기록 |
| checkout 동시성 + 물리 재고 분리 | available / reserved / committed 의 상태 머신 |
| 재고 부재 발견 시점이 출고 직전 | 결제 후 재고 확정 단계 에 최종 검증 (이미 commit 된 차감이 실 재고로 못 만들면 자동 환불 + 셀러 페널티) |
2. 쿠폰·프로모션 — 조합 폭발 + 부담 책임
2-1. 조합 폭발의 본질
쿠폰 하나는 단순하다 — “10% 할인”. 그런데 현실:
- 회원 등급 쿠폰 (브론즈/실버/골드)
- 첫 구매 쿠폰
- 카테고리 쿠폰 (식품 5%)
- 셀러 쿠폰 (특정 셀러만)
- 카드사 쿠폰 (특정 카드만)
- 시간대 쿠폰 (“10시~12시”)
- 묶음 쿠폰 (“3개 사면 1개 무료”)
- 적립금
- 무료배송 쿠폰
- 쿠폰 + 쿠폰 중복 가능 / 불가능 정책
상품 1 개 결제에 적용 가능한 쿠폰 조합이 수십 종. 그중 최적 조합을 자동 추천 할지 사용자가 선택 할지 결정해야 한다. 자동 추천하면 “왜 다른 쿠폰을 안 썼냐” 분쟁. 사용자 선택이면 “몰랐다” 클레임.
2-2. 부담 책임 이라는 진짜 비기술 문제
10% 할인을 누가 부담 하나?
- 플랫폼 부담 (마케팅 비용) — 셀러 정산은 원가 그대로
- 셀러 부담 — 셀러가 자기 마진에서 빼 줌
- 카드사 부담 — 카드사 제휴 마케팅
- 분담 — 셀러 5% + 플랫폼 5%
이 결정은 코드가 아니라 *합의서. 마케팅 / MD / 셀러 / CFO 사이에서 사전 협상. 그런데 *대규모 프로모션 때 협상이 PG 등록 시점에 끝나지 않은 채 출시 되는 일이 빈번. 정산 시점에 “누가 부담하기로 했지” 가 분쟁의 핵.
2-3. 기술 설계 침투 — 모든 할인의 부담 주체 명시화
코드에 coupon 의 부담 분배 를 적용 시점에 박제:
discount_application
id, order_id, line_item_id, coupon_id,
amount, bearer_type, bearer_share_bps
order_id | line_item_id | coupon_id | amount | bearer_type | bearer_share_bps
---------+--------------+-----------+--------+-------------+------------------
O-001 | LI-1 | C-A | 5000 | platform | 10000 (100%)
O-001 | LI-1 | C-B | 3000 | seller | 10000 (100%)
O-001 | LI-2 | C-C | 2000 | platform | 5000 (50%)
O-001 | LI-2 | C-C | 2000 | seller | 5000 (50%)
적용 시점의 *부담 정책 스냅샷. 정책이 이후에 바뀌어도 이 주문의 정산 계산은 적용 시점 정책으로 영원히 동일. 과거 정산을 재계산할 일이 없도록 정책을 불변 데이터 로 박제.
이게 settlement 시스템 에서 마지막에 가장 어려운 부분. 결제 금액은 단순 계산이지만 정산 금액 은 그 결제에 적용된 *모든 할인의 부담 주체별 분배 + 수수료율 + 환불 시 누구한테서 차감. 이걸 주문 1 건 = 정산 계산 1 줄 이 아니라 주문 1 건 = N 줄의 정산 항목 으로 모델링.
2-4. 핵심 패턴
| 비기술 문제 | 코드 패턴 |
|---|---|
| 쿠폰 부담 합의 가 정산 시점에야 분쟁 | discount_application 에 부담 주체 + 분담률 적용 시점 박제 |
| 쿠폰 정책 변경 시 과거 거래 영향 우려 | 정책을 immutable snapshot 으로 저장 |
| 쿠폰 조합 폭발 의 UI 복잡도 | 백엔드는 모든 조합 평가 가능, UI 는 추천 1개 + 상세 선택 분리 |
3. 정산 — 회계·세무 의 복잡도
3-1. 정산 = 단순 송금 아님
순진한 정산:
정산금액 = 결제금액 × (1 - 수수료율)
현실:
- 카드 PG 수수료 2.5% (체크카드 / 신용카드 / 간편결제 다름)
- 플랫폼 수수료 3% ~ 12% (카테고리·셀러 등급별 차등)
- 카드 매출세금계산서 발행 의무 (사업자 셀러)
- 부가세 신고 — 셀러가 사업자면 부가세 별도 처리
- 원천징수 — 일부 셀러는 사업자가 아니라 개인사업자/프리랜서 → 3.3% 원천징수
- 정산 주기 합의 — 익월 15일 / 매주 수요일 / 주문 후 7일 …
- 분리 정산 — 묶음배송이라도 셀러별로 정산
- 연말정산 / 종합소득세 신고용 자료 제공
3-2. 회계 부서가 묻는 질문
진짜 어려움은 회계팀의 과거 거래에 대한 정확한 답변:
- 2026 년 3 월에 셀러 A 에게 지급된 총액은?
- 그 중 부가세 별도는?
- 원천징수 금액은? 그 셀러는 사업자인가 개인인가?
- 환불된 거래의 회계 처리는?
- 프로모션 부담 비용은 마케팅 비용으로 잡혔나?
세무조사 시 이 질문들에 근거 자료 를 7 년 보관 해야 한다. 코드가 7 년 후에도 같은 답 을 내야 한다.
3-3. 기술 설계 침투 — immutable 정산 ledger
정산은 덮어쓰는 데이터가 아님. 모든 정산 결과는 append-only ledger. 환불·취소 시 이전 항목 수정 X — 역분개 항목 추가.
settlement_ledger
id, seller_id, occurred_at, account_type, amount, currency,
ref_type, ref_id, idempotency_key, posted_by
seller_id | type | amount | ref_type | ref_id
----------+----------+---------+-------------+--------
S-A | SALE | +50000 | order | O-001
S-A | PG_FEE | -1250 | payment | P-001
S-A | FEE | -1500 | settlement | ST-001
S-A | VAT_OUT | -4545 | settlement | ST-001
S-A | REFUND | -50000 | refund | R-001 (역분개)
S-A | PG_FEE | +1250 | payment | P-001 (역분개)
S-A | FEE | +1500 | settlement | ST-001 (역분개)
S-A | VAT_OUT | +4545 | settlement | ST-001 (역분개)
잔액 = SUM(amount). 복식부기 처럼 모든 거래를 *append 한다. 7 년 후 누군가가 *“이 셀러의 2026-03 매출은?” 이라 물으면 해당 월 범위의 SALE 합 으로 즉답 가능. 세무조사 대응 가능.
3-4. 핵심 패턴
| 비기술 문제 | 코드 패턴 |
|---|---|
| 회계 7 년 보관 + 정확한 과거 답변 | append-only ledger, 수정 X — 역분개 추가 |
| 셀러별 사업자 / 개인 / 원천징수 구분 | seller 의 세무 타입 을 시점 별 effective_from 으로 보관 |
| PG 수수료 / 플랫폼 수수료 / VAT 분리 | account_type 별 별도 행 으로 분개 |
4. 환불 — 법정 7 일 룰 과 회계 처리
4-1. 법적 기한 — 7일 단순청약철회
전자상거래법 17조: 소비자는 *물품 수령 후 7일 이내 단순 변심 으로 청약 철회 가능. 셀러가 거부 못 한다. 예외 (개봉 후 가치 훼손 / 디지털 콘텐츠 등) 가 있지만 *기본 정책은 7 일 무조건 환불. 즉 결제 후 7 일 + 배송 기간 = 약 14 일 동안 모든 거래는 “환불될 수 있는 미확정 상태”.
이게 정산에 침투. 결제 후 즉시 셀러에게 송금 하면 나중 환불 시 셀러로부터 회수 해야 함 — 회수 안 되는 셀러 도주 위험. 그래서 정산 보류 기간 이 필수.
4-2. 기술 설계 침투 — 정산 상태 머신 + 보류 기간
order: PAID → SHIPPED → DELIVERED → SETTLED
↓
REFUND_REQUESTED → REFUNDED
각 상태에 정산 가능 여부 가 다르다:
- PAID → 정산 불가 (배송 안 됨)
- SHIPPED → 정산 불가
- DELIVERED → 환불 가능 기간 (D+7) 동안 정산 보류
- DELIVERED + 7일 → 자동 SETTLED — 정산 가능
- REFUND_REQUESTED → 정산 역분개
환불 기간 동안의 자금 은 플랫폼 계좌 에 예치. 셀러에게 미지급. 셀러는 정산이 보름 늦어진다는 사실에 불만 — 그러나 법적 의무 라 협상 불가.
복잡한 환불 케이스:
- 부분 환불 — 묶음배송 3 개 중 1 개만 환불
- 배송비 환불 — 셀러 책임 / 구매자 책임에 따라 다름
- 쿠폰 사용 거래의 환불 — 사용한 쿠폰을 복원 할지 소진 할지
- 적립금 사용 거래의 환불 — 적립금 차감 후 환불할 금액 산정
이 복잡도가 settlement 시스템 의 가장 큰 코드 surface.
4-3. 핵심 패턴
| 비기술 문제 | 코드 패턴 |
|---|---|
| 법정 7 일 동안 정산 보류 | order 의 상태 머신 + 정산 가능 시점 별도 컬럼 |
| 환불 시 복원 / 소진 결정 분기 | refund_policy 의 coupon_restore / point_restore / shipping_refund 명시 |
| 부분 환불 의 정산 역분개 | settlement_ledger 의 line_item 단위 분개 |
5. 외부 의존 — PG / 카드사 / 3PL 의 통제 불가
5-1. 우리가 통제 못 하는 시스템들
이커머스는 수많은 외부 시스템에 의존:
- PG (Payment Gateway) — 토스페이먼츠 / 나이스 / KG이니시스 …
- 카드사 — 신한·국민·우리·하나… 각각의 점검 시간
- 간편결제 — 카카오페이 / 네이버페이 / 페이코 …
- PG 의 *PG — 해외카드는 GlobalPG 거쳐 발급사로
- 3PL (Third-Party Logistics) — 택배사 연동 API 가 각자 다른 스펙
- 물류센터 — 자체 또는 풀필먼트 업체
- 세무·회계 — 더존·SAP 등 ERP 연동
- 알림톡 / SMS / 메일 — 외부 통신사
- 주소·지번 — 행자부 API
- 지도 — 카카오맵 / 네이버지도
우리가 코드를 잘 짜도 이 외부 시스템이 다운 되면 기능이 죽는다. 카드사 1 개 점검만으로 결제 5% 가 실패한다.
5-2. 외부 시스템은 *언제든 스펙을 바꾼다*
- PG 의 결제 API 가 v1 → v2 으로 변경
- 카드사 3DS 인증 정책 강화
- 택배사 송장 조회 API 인증 방식 변경
- 수수료율 인상
이 변경을 우리가 의사결정 X. 통지받고 따라가야 함.
5-3. 기술 설계 침투 — adapter 분리 + Circuit Breaker + Outbox
이게 Hexagonal Architecture 가 진짜 정공 인 이유. 외부 의존을 *port 로 추상화* + adapter 가 실제 구현 — 외부 스펙 변경 시 adapter 만 교체. 코어 도메인은 PG 가 토스든 이니시스든 모름.
추가로 Resilience4j 의 Circuit Breaker — 외부 시스템 5xx 연속 시 자동 차단 + fallback. Bulkhead — 외부 호출 thread pool 격리 → 한 외부 시스템 hang 이 다른 기능 마비 안 시킴.
Outbox + Kafka — 결제 완료 이벤트를 우리 시스템에 기록 + 다른 모든 시스템 (정산 / CS / 마케팅) 은 이벤트로 받음. 외부 시스템 다운 시에도 우리는 이벤트 발행 → DB 안에 보존 → 외부 복구 시 retry.
5-4. 핵심 패턴
| 비기술 문제 | 코드 패턴 |
|---|---|
| 외부 시스템 스펙 변경 빈번 | port / adapter 분리, adapter 교체로 끝 |
| 외부 시스템 다운 → 우리도 다운 | Circuit Breaker + Bulkhead + fallback |
| 외부 시스템 retry 정책 + at-least-once | Outbox + 멱등 처리 + DLQ |
6. 이해관계자 충돌 — 셀러 vs 구매자 vs 플랫폼 vs CS vs MD vs 엔지니어
6-1. 진짜 비기술
이커머스의 모든 결정 은 최소 4 명의 의견 충돌:
| 이해관계자 | 우선순위 |
|---|---|
| 셀러 | 정산 빨리, 수수료 낮게, 노출 많이 |
| 구매자 | 가격 싸게, 배송 빠르게, 환불 쉽게 |
| 플랫폼 (CEO/CFO) | 수익률, 시장점유율, 브랜드 |
| CS (Customer Service) | 클레임 적게, 처리 빠르게 |
| MD (Merchandising) | 카테고리 다양화, 차별화 상품 |
| 마케팅 | 프로모션 효과 측정 |
| 법무 | 분쟁 최소화, 규제 준수 |
| 재무 | 정확한 정산, 빠른 결산 |
| 엔지니어 | 유지보수성, 시스템 안정성 |
같은 사고 에 대해 각자 다른 해석. 셀러가 어드민에서 가격을 0 원으로 잘못 등록 하고 그 시간에 5000 명 주문 했다 — 플랫폼은 환불? 강제 취소? 그대로 출고?
- 셀러: 부도나니까 취소 해주세요
- 구매자: 이미 결제했는데?
- CS: 전화 폭주, 처리 시간 부족
- 법무: 판매자 일방 취소는 위험
- 재무: 환불 처리 비용
- 엔지니어: 0원 등록 자체를 막을 수 있게 검증 추가하자
6-2. 기술 설계 침투 — 권한 / 감사 / 상태 머신
누가 무엇을 결정할 수 있는지 가 역할 기반 권한 으로 명시:
admin_user.role:
SUPER_ADMIN | 모든 권한
MD | 상품 관리, 정산 조회
CS | 환불 처리, 주문 변경 (단 셀러 동의 필요)
FINANCE | 정산 보고서, 세금계산서
MARKETING | 쿠폰 / 프로모션 등록
LEGAL | 분쟁 케이스 검토
모든 권한 행사 는 audit_log 에 기록:
audit_log
who | action | target | when | reason
----+-------------------+-----------------+-----------------+-----------------
CS1 | REFUND_FORCE | order O-001 | 2026-06-08 10am | seller agreed via phone
MD1 | PRICE_OVERRIDE | product P-100 | 2026-06-08 11am | bulk import error fix
“왜 그랬는지” 가 코드에 영원히 남는다. 분쟁 시 근거 자료 로 활용. 셀러가 1 년 후 *“내가 그때 동의 안 했다” 라 하면 audit_log 의 reason 으로 반박 가능*.
6-3. 핵심 패턴
| 비기술 문제 | 코드 패턴 |
|---|---|
| 각 부서의 권한 충돌 | RBAC (role-based access control) + 명시된 분리 |
| 결정의 사후 분쟁 | audit_log 의 who / action / target / when / reason |
| 예외 처리의 일관성 | 예외 처리도 상태 머신의 transition 으로 코드화 |
7. 어뷰징 / 사기 / 분쟁 — 고객의 비합리적 행동
7-1. 대다수는 정상이지만 일부는 적극적 어뷰징
- 쿠폰 다중 사용 — 가족 계정으로 첫 구매 쿠폰 반복
- 환불 사기 — 받았는데 안 받았다 주장
- 리뷰 조작 — 5 점 리뷰 1 원 알바
- 경쟁사 *별점 테러
- 가격 오류 (0 원 등록) 대량 주문 — 셀러 책임 이지만 플랫폼이 떠안음
- 재고 부족 시 *암표상 — 한정판 수십개 매입 후 외부 재판매
- 셀러의 자기 거래 — 매출 보이려고 자기 상품 자기가 결제 + 환불
7-2. 기술 설계 침투 — 이상 탐지 + 한도 + 사후 분석
순진한 시스템은 모든 요청을 신뢰. 정공은:
- rate limit (시간당 N 회) — 같은 IP / 같은 카드 / 같은 디바이스
- 행동 패턴 점수 — 신규 계정 / 빈번한 환불 / 짧은 클릭 간격
- 블랙리스트 — 사기 이력 카드 / IP / 디바이스
- 2 차 인증 — 고가 결제 시 SMS / 본인 인증
- 셀러 자기 거래 탐지 — 같은 IP 의 매출 패턴 검증
이걸 코어 결제 경로 에 직접 박지 말고 — fraud-detection 서비스 를 별도 분리 해서 결제 후 비동기 분석 + 의심 거래 hold. false positive 가 진짜 거래를 막는 것 이 더 큰 손실이라 결제는 일단 허용 + 사후 검토 패턴이 정공.
7-3. 핵심 패턴
| 비기술 문제 | 코드 패턴 |
|---|---|
| 어뷰징은 점점 정교화 | rule + ML 점수 + 사람 검토의 3 단계 |
| false positive 가 진짜 거래 차단 | 결제 허용 + 사후 hold/review |
| 분쟁 시 증거 부족 | 모든 결정 디바이스 fingerprint + IP + 행동 로그 보존 |
8. 그 모든 것이 *기술 설계 에 침투하는 4 가지 공통 패턴*
위 7 가지 비기술 문제가 결국 코드 설계 로 어떻게 침투하는지 — 4 가지 패턴으로 수렴한다.
8-1. 상태 머신 + audit_log (사후 답변 가능성)
분쟁 / 회계 / 법무 가 6 개월 후에 묻는 질문 에 답하려면 모든 상태 변화 가 누구에 의해 언제 발생했는지 기록되어야 함. order → payment → settlement → refund 의 각 상태 transition 에 audit_log 가 따라간다. 상태 변경 = 1 코드, audit 기록 = 또 다른 1 코드 가 아니라 상태 변경 메서드 자체가 audit 을 강제 하는 구조.
8-2. immutable snapshot + append-only ledger (시점 별 진실)
쿠폰 / 정산 / 수수료 / 정책. 지금 정책 과 그 거래가 발생했을 때의 정책 이 다르다. 거래 시점의 정책을 immutable snapshot 으로 박아두지 않으면 6 개월 후 정산 계산이 달라진다. ledger 도 마찬가지 — 환불은 기존 항목 수정 이 아니라 역분개 항목 추가. 영원히 무엇이 일어났는지 추적 가능.
8-3. port / adapter + 정책 외부화 (변경 흡수)
외부 의존 (PG / 카드사 / 3PL) 의 스펙 변경 빈도가 높다. 이걸 코어 도메인 에 박아두면 PG 1 곳 바꾸려고 도메인 전체 리팩토링. Hexagonal 의 outbound port + adapter 가 진짜 효과를 보는 영역. 마찬가지로 수수료율 / 쿠폰 정책 / 환불 정책 도 코드 상수가 아니라 DB / config 의 *시점별 active 항목* 으로.
8-4. 멱등 + 재시도 가능한 워크플로 (실패에서의 복구)
이커머스의 모든 외부 통신 은 언젠가 실패. 결제 OK 인데 PG 응답 못 받은 상태, 송금 OK 인데 cron 다운, 배송 처리 OK 인데 알림 발송 실패. 모든 endpoint 가 idempotency_key 받고 멱등 보장. 실패한 단계는 그 단계만 재시도 가능 — 전체 trans 다시 안 함. Outbox + Kafka + processed_events 의 3 단 멱등 방어 가 이 패턴의 구체화.
9. 마무리 — 기술 + 비기술 의 통합 사이클
“이커머스의 진짜 어려움은 *코드가 풀지 못하는 문제 가 코드에 흔적을 남기는 방식 을 설계하는 것”. 우리는 *재고 drift 를 막을 수 없지만 *원인을 추적할 수 있고, 쿠폰 부담 책임 합의 를 강제할 수 없지만 *적용 시점 정책을 박제할 수 있고, 외부 시스템 다운을 막을 수 없지만 *우리 시스템이 같이 안 죽도록 분리할 수 있다*.
좋은 이커머스 시스템의 신호:
- 6 개월 전 거래 에 “어떻게 됐었지?” 라는 질문에 5 분 안에 답 할 수 있는가
- 새 PG 추가 가 2 시간 안에 PR 1 개 인가
- 셀러 1000 명 의 정산이 월말 1 일 안에 끝나고 수정 가능 한가
- 사고 발생 시 되돌리기 가 사람 없이 가능한가
- 부서 간 결정이 코드에 기록 되어 분쟁 시 근거 가 되는가
“진짜 좋은 시스템은 기술적으로 화려한 게 아니라 *비기술 문제가 닥쳤을 때 답할 수 있는 시스템”* — 이 글이 코드 작성보다 그 답할 수 있는 구조 설계 를 한 단계 더 의식하게 해줬길.
이커머스 도메인의 기술적 글 (Hexagonal / Outbox / MSA 분리 / GitOps) 을 이전 글에서 다뤘다면, 이 글은 그 모든 기술이 결국 풀려고 했던 비기술 문제들 의 지도. 어느 한 쪽만 보면 과도 설계 거나 과소 설계. 둘이 만나는 지점 이 진짜 정공이다.