2026. 7. 16. 15:51ㆍ카테고리 없음
DDD를 공부하며 직접 API 22개를 테스트해본 기록과 설계에 대한 정리
백엔드 프로젝트를 진행하면서 리뷰 기능을 중심으로 전체 기능을 직접 테스트해봤다.
그리고 이번 프로젝트를 진행하면서 DDD(Domain Driven Design)를 왜 사용하는지, 어떤 방식으로 설계해야 하는지에 대해서도 함께 정리해봤다.
이번에 테스트한 API
1. 회원 기능
회원 기능은 CUSTOMER, OWNER, MASTER 세 가지 권한을 기준으로 테스트했다.
회원가입
- CUSTOMER 회원가입
- OWNER 회원가입
로그인
- CUSTOMER 로그인
- OWNER 로그인
- MASTER 로그인
사용자 정보
- JWT 토큰으로 내 정보 조회
회원 탈퇴
- 비밀번호 오류
- 정상 탈퇴
2. 마스터 데이터 생성
가게를 등록하기 위해서는 지역과 카테고리가 먼저 존재해야 한다.
MASTER 권한으로 다음 API를 테스트했다.
- 지역 생성
- 카테고리 생성
3. 가게 및 메뉴
OWNER 권한으로 가게를 등록하고 메뉴를 생성했다.
- 가게 등록
- 가게 오픈
- 메뉴 카테고리 생성
- 메뉴 생성
4. 장바구니 및 주문
실제 주문이 생성되는 흐름을 테스트했다.
- 장바구니 생성
- 장바구니에 메뉴 추가
- 체크아웃 → 주문 생성(PENDING)
5. 주문 상태 변경
리뷰를 작성하기 위해서는 주문이 COMPLETED 상태까지 변경되어야 한다.
다음 순서대로 테스트했다.
PENDING
↓
ACCEPTED
↓
COOKING
↓
READY
↓
COMPLETED
총 4단계의 상태 전이를 모두 확인했다.
6. 리뷰
이번 프로젝트 내 테스트 대상이다.
리뷰 작성
- 정상 작성
- 중복 작성(R002)
- 삭제 후 재작성
전체 조회
처음에는 500 에러가 발생했지만 수정 후 정상적으로 조회되는 것을 확인했다.
검색 및 필터
다음 조건을 조합하여 테스트했다.
- keyword
- minStar
- storeId
단건 조회
- 답글 포함
- 이미지 포함
가게별 리뷰 조회
특정 가게의 리뷰 목록을 조회했다.
리뷰 수정
- 별점만 수정
- 이미지 3장 등록
- 이미지 1장 교체
리뷰 삭제
다양한 권한을 테스트했다.
- 본인 삭제
- OWNER 삭제 차단
- MASTER 운영 개입
- 권한 오류
사장 답글
- 정상 작성
- 중복 작성(RC002)
테스트 결과
총 22개의 엔드포인트를 테스트했다.
사용한 계정은
- CUSTOMER
- OWNER
- MASTER
총 3개의 권한이다.
이번에 테스트하지 않은 API
아래 기능들은 이번 테스트 범위에서는 제외했다.
리뷰
- 답글 수정
인증
- Refresh Token 재발급
- 로그아웃
사용자
- 회원 검색
- 프로필 수정
결제
주문이 결제 없이도 COMPLETED까지 진행되는 구조였기 때문에 결제 API는 테스트하지 않았다.
DDD란 무엇일까?
DDD(Domain Driven Design)는 단순히 프로젝트 구조를 나누는 방법이 아니다.
회사의 비즈니스를 이해하고, 그것을 코드로 표현하는 설계 방식이다.
즉,
비즈니스를 먼저 이해하고, 그 이해를 코드에 그대로 녹여내는 것이 DDD의 핵심이다.
AI가 잘하지 못하는 영역
최근에는 AI가 코드를 굉장히 잘 작성한다.
하지만 아직도 사람이 직접 고민해야 하는 영역이 있다.
대표적으로
- 복잡한 도메인 분석
- 회사의 핵심 비즈니스 이해
- 데이터 일관성과 불변식 설계
- 시스템 전체 구조 설계
결국 좋은 개발자는
비즈니스를 이해하고 설계할 수 있는 개발자
라고 생각한다.
앞으로 지향해야 할 개발 방식
내가 생각하는 개발 순서는 다음과 같다.
비즈니스 이해
↓
도메인 정의
↓
DDD 모델링
↓
설계 반영
↓
구현
↓
비즈니스 기준으로 검증
좀 더 풀어서 보면
- 회사와 업계를 이해한다.
- 핵심 도메인을 정의한다.
- 바운디드 컨텍스트를 나눈다.
- 애그리거트를 설계한다.
- 모델을 기반으로 프로젝트 구조를 만든다.
- 코드로 구현하고 비즈니스 기준으로 검증한다.
우리가 흔히 겪는 문제
프로젝트가 커질수록 자주 발생하는 문제들이 있다.
스파게티 의존성
서비스가 서로 계속 참조하면서
"도대체 어디서 데이터를 가져오는 거지?"
라는 상황이 발생한다.
도메인 경계가 모호하다
하나의 Service 안에서
- UserRepository
- StoreRepository
- ReviewRepository
- OrderRepository
등 여러 Repository를 동시에 사용하기 시작한다.
그러다 보면
수정 하나가 예상하지 못한 곳까지 영향을 주게 된다.
일관성 없는 코드
개발자마다 구현 방식이 달라진다.
확장이 어려운 구조
프로젝트가 커질수록 모든 코드가 서로 엮여 수정이 어려워진다.
강결합과 느슨한 결합
강결합 구조에서는
모든 서비스가 서로 직접 참조한다.
하나를 수정하면 연쇄적으로 다른 코드까지 영향을 받는다.
반대로 느슨한 결합에서는
각 도메인이 독립적으로 존재한다.
예를 들어
- User Context
- Order Context
- Review Context
각 컨텍스트는 자신의 책임만 가진다.
다른 컨텍스트와는 인터페이스를 통해 통신한다.
바운디드 컨텍스트(Bounded Context)
DDD에서는 가장 먼저
"경계를 어디까지 나눌 것인가?"
를 고민한다.
예를 들어 User라는 개념 하나만 봐도
역할에 따라 의미가 달라진다.
- 주문자
- 점주
- 리뷰 작성자
- 결제 주체
모두 같은 사람이지만
비즈니스에서는 서로 다른 역할을 수행한다.
즉,
같은 User라도 컨텍스트가 달라지면 의미가 달라진다.
다른 컨텍스트는 어떻게 접근할까?
모놀리식에서는 보통 Facade를 이용한다.
UserDto user = userFacade.getUserById(id);
Order가 UserRepository를 직접 사용하는 것이 아니라
Facade를 통해 접근한다.
MSA에서는
- REST API
- 이벤트
등을 이용해 통신한다.
애그리거트(Aggregate)
DDD에서 가장 중요한 개념 중 하나다.
주문을 예로 들어보자.
주문에는
- 주문 상품
- 배송 정보
- 주문자
가 존재한다.
이들은 혼자 존재하는 것이 아니라
항상 주문과 함께 움직인다.
그래서 하나의 애그리거트로 묶는다.
Order
├── OrderLine
├── ShippingInfo
└── Orderer
여기서 Order가 대표이다.
이 대표를
Aggregate Root
라고 한다.
왜 Aggregate Root가 필요한가?
잘못된 예를 보자.
order.getOrderLines().add(...);
이렇게 하면
주문 상품만 추가되고
총 금액은 변경되지 않을 수도 있다.
반면
order.addOrderLine(...);
처럼 Order를 통해 추가하면
- 상품 추가
- 총 금액 계산
- 주문 상태 검증
이 모두 함께 수행된다.
즉,
모든 변경은 Aggregate Root를 통해서만 이루어진다.
애그리거트는 어떻게 나눌까?
DDD에서는 세 가지 질문을 한다.
1. 항상 함께 변경되는가?
주문 상품이 추가되면
총 금액도 반드시 변경된다.
따라서 같은 애그리거트이다.
2. 같은 생명주기를 가지는가?
Order가 삭제되면
OrderLine도 함께 삭제된다.
같은 생명주기를 가진다.
반면 Product는
Order가 삭제되어도 계속 존재한다.
따라서 다른 애그리거트이다.
3. 같은 비즈니스 규칙을 공유하는가?
Order는
- 총 금액 계산
- 배송 시작 후 수정 금지
- 최소 주문 수량
등을 책임진다.
반면 Product는
- 재고
- 가격
- 판매 여부
를 책임진다.
규칙이 다르므로 별도의 애그리거트이다.
애그리거트끼리는 ID로 참조한다
Order가 Product를 직접 가지고 있으면 문제가 생긴다.
예를 들어
노트북 가격이
100만원에서
120만원으로 변경되었다고 하자.
예전 주문도 함께 변경되어 버리면 큰 문제가 된다.
그래서 Order는 Product 전체를 저장하지 않는다.
OrderLine
productId
orderPrice
quantity
주문 당시의 가격만 저장한다.
Cascade를 고민해야 하는 이유
애그리거트를 설계할 때는
삭제 전파도 함께 고민해야 한다.
가능한 경우
Order 삭제
↓
OrderItem 삭제
불가능한 경우
Order 삭제
↓
Product 삭제
Product는 독립적인 애그리거트이므로 삭제되면 안 된다.
리포지터리는 Aggregate Root만 가진다
좋은 DDD 구조에서는
OrderRepository
는 존재하지만
OrderItemRepository
는 만들지 않는 경우가 많다.
외부에서는 항상 Aggregate Root를 통해 접근한다.
DDD의 전체 구조
DDD를 계층으로 표현하면 다음과 같다.
도메인
↓
바운디드 컨텍스트
↓
애그리거트
↓
엔티티 / 값 객체
- 도메인은 무엇을 하는 분야인지 정의한다.
- 바운디드 컨텍스트는 그 분야를 어떤 규칙과 모델로 구현할지 결정한다.
- 애그리거트는 관련 객체를 하나의 일관성 단위로 묶는다.
- 엔티티와 값 객체는 실제 데이터를 표현한다.
정리
이번 프로젝트에서는 리뷰 기능을 중심으로 전체 주문 흐름을 직접 테스트하며 총 22개의 API를 검증했다.
단순히 API 호출에 그치지 않고, 주문 생성부터 완료, 리뷰 작성과 수정, 삭제, 사장 답글까지 실제 비즈니스 흐름을 따라가며 테스트했다.
또한 테스트 과정에서 DDD를 함께 학습하면서, 비즈니스를 중심으로 설계하는 것이 얼마나 중요한지 다시 느꼈다.
결국 좋은 코드는 단순히 동작하는 코드가 아니라, 비즈니스 규칙을 자연스럽게 표현하는 코드라고 생각한다.
프로젝트가 마무리된 후에는 결과물을 정리하고 발표를 준비하는 과정도 중요한 경험이었다.
프로젝트에서는 팀원 모두가 Canva를 활용하여 발표 슬라이드를 함께 제작했다.
각자 맡은 역할과 구현한 기능을 정리하고 하나의 발표 자료로 완성하는 방식으로 협업을 진행했다.
내일 할것
내일은 팀 프로젝트 전체 시나리오와 코드 구현을 확인하며 전체적인 개발 프로세스를 복습하고 배포실습(배포 자동화 및 EC2 S3 인스턴스 생성 및 슬랙 깃허브 연동)을 해볼것이다.
추천 도서
『도메인 주도 개발 시작하기』 - 최범균
DDD를 처음 접하는 사람에게 가장 추천하는 입문서다.
실무 중심으로 설명되어 있어 읽기 쉽다.
『도메인 주도 설계(Domain-Driven Design)』 - Eric Evans
DDD의 철학과 핵심 개념을 다루는 고전이다.
난이도는 높지만 복잡한 실무 시스템을 설계하는 관점을 배울 수 있는 책이다.