상품/카테고리/고객/장바구니/주문/쿠폰
9장에서 확정한 P0·P1 유스케이스를 구현하려면 데이터 구조가 필요하다. 도메인 객체(Domain Object)는 비즈니스 개념을 표현하는 데이터 덩어리다. 쇼핑몰에서는 상품·카테고리·고객·장바구니·주문·쿠폰이 핵심 객체다.
도메인 모델링 목적은 FO·BO 화면 설계와 개발팀의 DB 스키마 설계에 공통 언어를 제공하는 것이다. "상품 상세 화면에 브랜드명을 표시한다"고 말할 때, Product 객체에 brand 속성이 있어야 한다는 합의가 필요하다. 속성이 정의되지 않으면 화면 설계와 개발 사이에 괴리가 생긴다.
이 장에서는 각 객체의 필수 속성과 데이터 타입, 제약 조건을 정의한다. 7장 인터뷰 답변과 8장 유스케이스를 바탕으로 작성하며, 모든 속성은 FO·BO 어디선가 사용되는 것만 포함한다.
상품은 쇼핑몰의 중심 객체다. FO에서는 리스트·상세·장바구니에 표시되고, BO에서는 등록·수정·재고 관리에 사용된다.
필수 속성
기본 정보
product_id (문자열, 고유 식별자): SKU 코드, 예: "PROD-2025-001"
product_name (문자열, 최대 100자): 상품명
category_id (문자열, 카테고리와 연결): 소속 카테고리
brand (문자열, 최대 50자): 브랜드명
description (텍스트, 최대 5,000자): 상세 설명
가격·재고
price (정수): 정가, 단위 원
sale_price (정수, 선택): 할인가, 없으면 정가로 판매
stock_quantity (정수): 현재 재고 수량
stock_status (열거형): 판매 중·품절·단종, 값 ['available', 'sold_out', 'discontinued']
이미지·미디어
images (배열): 이미지 URL 목록, 최대 10개
thumbnail (문자열): 썸네일 이미지 URL (리스트용)
video_url (문자열, 선택): 유튜브 URL
메타 정보
created_at (날짜시간): 등록 일시
updated_at (날짜시간): 최종 수정 일시
is_visible (불린): FO 노출 여부, true/false
상품에 옵션(색상·사이즈)이 있으면 별도 객체로 관리한다.
ProductOption
option_id (문자열)
product_id (문자열, 상품과 연결)
option_name (문자열): 옵션명, 예: "색상"
option_value (문자열): 옵션값, 예: "블루"
price_diff (정수, 선택): 가격 차이, 예: +5,000원
stock_quantity (정수): 옵션별 재고
예시: 티셔츠 상품에 색상(빨강·파랑·검정) × 사이즈(S·M·L) 조합이면 9개 ProductOption 레코드가 생성된다.
제약 조건
product_name은 중복 가능 (같은 이름 상품이 다른 브랜드에 있을 수 있음)
price는 0 이상, sale_price는 price보다 낮거나 같아야 함
stock_quantity가 0이면 stock_status는 자동으로 'sold_out'
images 배열은 최소 1개 이상 필수
카테고리는 상품을 분류하는 계층 구조다. 3depth까지 지원한다(상위·중위·하위).
필수 속성
category_id (문자열, 고유 식별자)
category_name (문자열, 최대 50자): 카테고리명
parent_id (문자열, 선택): 상위 카테고리 ID, 없으면 최상위
depth (정수): 깊이, 1(상위)·2(중위)·3(하위)
sort_order (정수): 정렬 순서
is_visible (불린): FO 노출 여부
계층 구조 예시
의류 (depth=1, parent_id=null)
├─ 상의 (depth=2, parent_id=의류)
│ ├─ 티셔츠 (depth=3, parent_id=상의)
│ └─ 블라우스 (depth=3, parent_id=상의)
└─ 하의 (depth=2, parent_id=의류)
├─ 청바지 (depth=3, parent_id=하의)
└─ 스커트 (depth=3, parent_id=하의)
제약 조건
최대 depth는 3
parent_id가 있으면 부모 카테고리가 존재해야 함
순환 참조 불가 (A가 B의 부모이고 B가 A의 부모인 경우)
고객은 FO에서 주문하는 사용자다. 회원과 비회원을 구분한다.
필수 속성
기본 정보
customer_id (문자열, 고유 식별자)
email (문자열, 고유): 로그인 ID 겸용
password_hash (문자열): 암호화된 비밀번호
name (문자열, 최대 50자): 이름
phone (문자열): 휴대폰 번호, 형식 "010-1234-5678"
등급·마케팅
customer_grade (열거형): 등급, 값 ['silver', 'gold', 'vip']
total_purchase_amount (정수): 누적 구매 금액
marketing_consent (불린): 마케팅 정보 수신 동의 여부
consent_date (날짜시간, 선택): 동의 일시
메타 정보
created_at (날짜시간): 가입 일시
last_login_at (날짜시간): 최근 로그인
is_active (불린): 활성 계정 여부
별도 객체로 관리해 여러 배송지를 저장할 수 있다.
CustomerAddress
address_id (문자열)
customer_id (문자열, 고객과 연결)
recipient_name (문자열): 수령인 이름
phone (문자열): 연락처
address (문자열): 주소
detail_address (문자열): 상세 주소
is_default (불린): 기본 배송지 여부
제약 조건
email은 형식 검증 필수 (정규식)
phone은 "010-XXXX-XXXX" 형식
total_purchase_amount는 주문 완료 시 자동 증가
customer_grade는 total_purchase_amount 기준으로 자동 산정 (예: 50만 원 이상 Gold)
장바구니는 고객이 구매 전 상품을 임시 저장하는 공간이다. 세션 단위로 관리한다.
필수 속성
CartItem (개별 상품 항목)
cart_item_id (문자열, 고유 식별자)
customer_id (문자열, 고객과 연결): 회원일 경우, 비회원은 session_id 사용
product_id (문자열, 상품과 연결)
option_id (문자열, 옵션과 연결): 옵션이 있는 경우
quantity (정수): 수량, 최소 1
added_at (날짜시간): 담은 일시
계산 속성
FO에서 장바구니 총액을 표시할 때 사용하는 계산값이다. DB에 저장하지 않고 실시간 계산한다.
item_price: (상품 가격 + 옵션 가격 차이) × 수량
total_price: 모든 CartItem의 item_price 합계
discount_amount: 쿠폰 적용 시 할인 금액
final_price: total_price - discount_amount + 배송비
제약 조건
같은 고객이 동일한 상품·옵션을 중복으로 담으면 수량만 증가
비회원 장바구니는 7일 후 자동 삭제
품절 상품은 장바구니에서 비활성화 표시
주문은 고객이 결제를 완료한 거래 기록이다. BO에서 가장 중요한 객체로, 상태 변경 이력을 추적한다. 아래 주문 상품과(OrderItem) 혼동이 있을 수 있다. 해당 객체는 대부분 Payment로 관리하지만, 여기에서는 배송정보도 포함되기에, Order로 관리한다.
필수 속성
기본 정보
order_id (문자열, 고유 식별자): 주문번호, 예: "ORD-20250120-001"
customer_id (문자열, 고객과 연결)
order_date (날짜시간): 주문 일시
order_status (열거형): 상태, 값 ['pending', 'paid', 'preparing', 'shipped', 'delivered', 'cancelled', 'refunded']
금액 정보
subtotal (정수): 상품 총액
discount_amount (정수): 할인 금액 (쿠폰)
shipping_fee (정수): 배송비
total_amount (정수): 최종 결제 금액
배송 정보
recipient_name (문자열): 수령인
phone (문자열): 연락처
address (문자열): 배송지
delivery_memo (문자열, 선택): 배송 메모
tracking_number (문자열, 선택): 운송장 번호
shipped_at (날짜시간, 선택): 출고 일시
delivered_at (날짜시간, 선택): 배송 완료 일시
결제 정보
payment_method (열거형): 결제 수단, 값 ['card', 'transfer', 'naverpay', 'kakaopay']
payment_status (열거형): 결제 상태, 값 ['pending', 'completed', 'failed', 'refunded']
paid_at (날짜시간, 선택): 결제 완료 일시
주문에 포함된 개별 상품 정보다.
order_item_id (문자열)
order_id (문자열, 주문과 연결)
product_id (문자열, 상품과 연결)
product_name (문자열): 주문 당시 상품명 (스냅샷)
option_name (문자열, 선택): 옵션명
price (정수): 주문 당시 가격
quantity (정수): 수량
subtotal (정수): price × quantity
주문 당시 상품명과 가격을 스냅샷(특정 시점의 상태를 그대로 캡처하는 것을 의미로 저장 당시의 데이터를 저장)으로 저장하는 이유는, 나중에 상품 정보가 변경되어도 주문 내역이 일치해야 하기 때문이다.
제약 조건
order_status는 순차적으로만 변경 가능 (pending → paid → preparing → shipped → delivered)
취소는 'paid', 'preparing' 상태에서만 가능
환불은 'delivered' 후 7일 이내에만 가능
total_amount = subtotal - discount_amount + shipping_fee 공식 유지
쿠폰은 마케팅 도구로 할인 혜택을 제공한다. 발급·사용·만료 상태를 관리한다. 쿠폰에 해당하는 정보만 기입한다.
필수 속성
쿠폰 정의
coupon_id (문자열, 고유 식별자)
coupon_name (문자열, 최대 100자): 쿠폰명
coupon_code (문자열, 선택): 쿠폰 코드 (수동 입력용)
discount_type (열거형): 할인 유형, 값 ['fixed', 'percentage'] (정액·정률)
discount_value (정수): 할인 값 (정액이면 원 단위, 정률이면 퍼센트)
min_purchase_amount (정수, 선택): 최소 구매 금액
max_discount_amount (정수, 선택): 최대 할인 금액 (정률 쿠폰 시)
유효 기간
valid_from (날짜시간): 사용 시작 일시
valid_until (날짜시간): 만료 일시
issue_limit (정수, 선택): 발급 수량 제한, null이면 무제한
적용 조건
target_customers (열거형): 대상 고객, 값 ['all', 'new', 'grade_silver', 'grade_gold', 'grade_vip']
target_categories (배열, 선택): 적용 카테고리 ID 목록, null이면 전체
발급된 쿠폰과 사용 내역을 추적한다.
customer_coupon_id (문자열)
coupon_id (문자열, 쿠폰과 연결)
customer_id (문자열, 고객과 연결)
issued_at (날짜시간): 발급 일시
used_at (날짜시간, 선택): 사용 일시
order_id (문자열, 선택): 사용한 주문 ID
status (열거형): 상태, 값 ['active', 'used', 'expired']
제약 조건
discount_type이 'percentage'면 discount_value는 1~100
valid_from은 valid_until보다 이전이어야 함
쿠폰은 1회 1매만 사용 가능 (used_at이 있으면 재사용 불가)
만료된 쿠폰(현재 시간 > valid_until)은 status가 'expired'로 자동 변경
도메인 객체는 서로 연결되어 있다.
주요 관계
Product → Category: 다대일 (여러 상품이 하나의 카테고리에 속함)
Product → ProductOption: 일대다 (한 상품에 여러 옵션)
Customer → Order: 일대다 (한 고객이 여러 주문)
Order → OrderItem: 일대다 (한 주문에 여러 상품)
Customer → CartItem: 일대다 (한 고객의 여러 장바구니 항목)
Coupon → CustomerCoupon: 일대다 (한 쿠폰이 여러 고객에게 발급)
관계를 명확히 하면 FO·BO 화면에서 어떤 데이터를 조회할지 결정할 수 있다. 예: 주문 상세 화면에서는 Order → OrderItem → Product 순으로 조인해 상품 정보를 가져온다.
이번 장은 거의 개발 수준에 가까운 문서이다. 하지만, 이것을 토대로 DB 개발자는 스키마(Schema)이라는 모델을 만든다. 기획자가 다하고 개발자는 그냥 만들기만 하는 것 아닌지 의문이 들 수 있다. 기획자가 필요한 정보를 항목으로 제공해 주면 개발자는 항목을 기반으로 여러 복잡한 관계도 설정을 진행한다.
DB에는 위에서는 열거한 항목만 존재하는 것이 아니다. 객체 간 relation과 참조 여부 등 테이블이 더 추가되고, 항목도 더 많아진다.
앞으로 기획자가 사전에 해야 할 작업이 몇 장 더 남아 있지만, 여기까지 봐서도 알 수 있듯이 프로젝트의 중심은 기획자여야 한다. 개발자가 중심이 되면 기능이 축소되고, 운영이 중심이 되면 과도한 기능이 될 수 있다.
기획자는 운영의 요구사항을 정리하고, 개발에 요청을 할 수 있도록 다방면으로 알고 있어야 한다.