DDD기반 ERD 설계 및 개선

야부엉·2025년 3월 4일

0. 목표

첫 MSA 기반 팀 프로젝트가 끝나고 난 후, DDD에 대해 다시 학습하고, 학습한 내용을 기반해서 기존의 프로젝트를 수정해 나가기 위해 작성했습니다.
프로젝트 주제는 티켓팅 서비스 구현입니다.

1. 기존 문제점 및 요구사항 분석

기존 ERD

기존 문제점은 다음과 같습니다.

  • 공연장과 좌석 정보가 없음
  • 티켓과 회원이 1:1 관계
  • 엔티티와 값 객체 구분이 없어 역할과 책임이 모호

일단 ERD 설계 이전 서비스 기능 및 요구사항을 아래와 같이 정리했습니다.

  1. 한 명의 유저는 여러 개의 좌석을 살 수 있다.
  2. 공연마다 시작일, 종료일, 예매 시작일, 예매 종료일이 다르다.
  3. 공연장 좌석마다 등급이 있고, 개수의 제한이 있다.
  4. 공연에 대한 리뷰를 남길 수 있다.
  5. 공연은 카테고리 별로 분류가 된다.

2. 설계 개선 및 주요 변경점

  • Hall과 Seat 테이블 추가
    기존 ERD에서 공연장과 좌석 정보가 누락되어 있었습니다.
    공연장은 물리적 공간 정보만 가지며, 좌석은 공연장에 고정된 물리적 자산입니다. 초기에 좌석을 '공연장의 값 객체'로 둘지 고민했지만,
    좌석마다 상태 관리(Ticket)가 필요하므로 좌석을 독립 엔티티로 변경했습니다.

    도메인역할
    seat공연장 내 물리적 좌석 정의 (구역/열/번호/등급)
    ticket특정 회차에서 특정 좌석의 '현재 예매 상태' 관리

    -> 좌석은 하드웨어, 티켓은 소프트웨어적인 상태 관리라는 점을 명확히 구분했습니다.

  • Order ↔ Ticket 직접 연결 문제와 OrderLine 도입
    한 명이 여러 좌석을 예매하는 요구사항을 고려했을 때,
    처음엔 Order ↔ Ticket을 1:N으로 연결하는 단순 구조를 고려했습니다. 하지만, 이 구조에서 "부분 취소"나 "정산 데이터 관리"가 매우 어려워지는 문제를 확인했습니다.

     ❌ Order ↔ Ticket 직접 연결 시 문제
    - 부분 취소 시, Order 상태와 Ticket 상태를 동기화하는 복잡한 로직 필요
    - 좌석별 가격/할인율/수수료 정보를 Order가 직접 관리해야 하는 비정상적 구조
    - 정산 시 좌석별 데이터 추출이 불편해짐
    - 예매 당시 가격/할인율과 현재 정보를 구분하기 어려움 (스냅샷 관리 부재)
    
    ✅ Order ↔ OrderLine ↔ Ticket 구조 도입
    - Order는 주문 단위 관리
    - OrderLine은 좌석별 상세 정보 및 정산 데이터 보관
    - Ticket은 현재 좌석 상태 관리

    -> OrderLine은 단순 연결이 아닌, 좌석별 가격/할인율/수수료 보존과 정산 데이터의 중심 역할을 담당하는 엔티티로 정의했습니다.

    OrderLine은 값 객체 vs 엔티티
    이 논쟁에 대해 가장 중요한 포인트는 맡은 역할에 따라 결정할 수 있습니다. 티켓(Ticket)의 역할은 "현재 좌석의 상태" 관리이고, OrderLine의 역할은 "과거 예매 기록 + 정산 정보" 관리입니다. 즉, 현재 서비스 과거 기록을 통해 회계적 요구가 필요한 경우에는 엔티티를 통해 표현하는 것이 정답입니다. 기존의 서비스를 참고해 보면, 티켓은 회차 종료 후 데이터가 없어지는 경우가 있고, OrderLine은 법적 보관 의무 등 특정 사유로 인해 장기적으로 보관되어야하기 때문에 엔티티로 보관하는 경우가 많습니다. 현재 프로젝트도 추후 확장성을 고려해 별도의 엔티티로 관리하는게 맞다고 생각했습니다.

  • TimeSlot 도입
    기존 ERD에는 공연 시간 개념이 없었습니다. 즉, 같은 날에 여러 공연을 할 경우 분리하는 방법이 없었습니다. 그래서 회차라는 테이블의 필요성을 느꼈고 공연 ↔ TimeSlot (회차 엔티티) 구조로 변경했습니다. TimeSlot은 공연 날짜/시간에 대해 관리하도록 설게했습니다.

  • 공연에 대한 가격정책
    공연에 대한 가격은 어떠한 공연인지에 따라 차이가 있다고 생각했습니다. 뿐만 아니라 seat 데이터는 물리적 정보만을 가지도록 책임을 분리하고 싶었습니다. 그래서 SeatPricePolicy 테이블을 추가해서 해당 TimeSlot에 관한 가격을 별도로 관리하도록 했습니다.

3. 연관관계 정리 및 최종 ERD

1. 도메인별 역할 정리

2. 최종 ERD

현재 DDD 기반이기 때문에 어그리게이터를 통해 도메인별 책임/경계를 명확하게 할 필요가 있다고 생각했습니다. 그래서 다음과 같이 설정했습니다.

  • 공연 서비스 Aggregate

    • 루트 엔티티: Performance
    • 하위 구성: TimeSlot, SeatPricePolicy, Review
  • 공연장 서비스 Aggregate

    • 루트 엔티티: Hall
    • 하위 구성: Seat
  • 주문 서비스 Aggregate

    • 루트 엔티티: Order
    • 하위 구성: OrderLine

3. 주문 프로세스 시나리오

  • 고객이 공연 상세 페이지에서 날짜와 시간 선택
  • 해당 회차(TimeSlot)의 좌석 목록 조회
  • Seat: 좌석 위치/등급 정보
  • SeatPricePolicy: 좌석 등급별 가격 정보 조회
  • 고객이 좌석 선택
  • 선택한 좌석으로 Order 생성 (주문 서비스)
  • Order 안에 OrderLine 추가 (선택 좌석/가격 스냅샷 보관)
  • 결제 완료 시
  • Order 상태 변경 (ORDERED)
  • Ticket 상태 변경 (AVAILABLE → SOLD)

4. 결론

✅ 도메인 간 역할/책임 분리로 서비스 경계 명확화
✅ 예매 당시 가격/수수료/할인율 보관으로 회계적 일관성 확보
✅ 부분 취소/정산 시, 좌석별 데이터 추적 가능
✅ 좌석/가격/상태 관리 책임을 각각 명확하게 분산
✅ MSA 구조에서 서비스 간 강결합 최소화 및 성능 확보


참고 자료

도메인 주도 개발 시작하기 (책)

profile
밤낮없는개발자

0개의 댓글