규칙들을 어떤 순서로 실행해야 하는가?
백엔드 기능은 여러 작업을 단순히 나열하는 것이 아니라, 실행 순서와 실패 시 남는 상태까지 생각해서 설계해야 한다.
예를 들어 주문 하나에도 `쿠폰 검증`, `재고 차감`, `포인트 차감`, `주문 저장` 과 같은 작업들이 있을 수 있다.
중요한 것은 "전부 다 실행하면 되지." 하는 게 아니다.
무엇을 먼저 해야 하지?
중간에 실패하면 어떻게 되지?
순서를 바꾸면 어떤 문제가 생기지?
Seqence Diagram이란?
누가 누구를 어떤 순서로 호출하는지 시간 흐름에 따라 그려놓은 그림
아주 단순한 상품 조회 API를 예로 들면,
Client
|
| GET /products/1
v
Controller
|
| findProduct(1)
v
ProductService
|
| findById(1)
v
Repository
|
| SELECT ...
v
Database
시간은 위에서 아래로 흐른다고 생각하면 된다.
그래서 코드를 아직 작성하지 않았어도, "요청이 들어오면 어떤 객체가 어떤 객체를 호출해야 하지?"를 먼저 생각해볼 수 있다.
왜 이런 그림을 그리는가?
주문 기능 요구사항만 보고 바로 `OrderService`를 만들었다고 치자.
그러면 코드 안에서 이런 고민을 시작할 수 있다.
coupon...?
product...?
point...?
orderRepository...?
그런데 Sequence Diagram을 먼저 생각하면 코드를 작성하기 전에 흐름을 놓고 업무 순서를 검토할 수 있다.
Client
↓
Controller
↓
Application Service
↓
?
↓
?
↓
Repository
즉 Sequence Diagram의 목적은 예쁜 UML 문서를 만드는 것이 아니라, 실행 흐름에서 빠진 작업이나 이상한 순서를 발견하는 것에 가깝다.
Controller부터 Repository까지 각각 무엇을 하는가?
Client
↓
Controller
↓
Application Service
↓
Domain
↓
Repository
Client
API를 호출하는 쪽. 웹 브라우저일 수도 있고 앱일 수도 있다.
"상품을 주문할게요."라고 요청한다.
Controller
HTTP 요청을 받는다.
POST /orders
그리고 요청 데이터를 Application Service로 전달한다.
Application Service
여러 작업의 전체 흐름을 조율한다.
`쿠폰 관련 작업`, `상품 관련 작업`, `포인트 관련 작업`, `포인트 관련 작업`, `주문 관련 작업`을 어떤 순서로 수행할지 관리한다.
Domain
`Coupon`, `Product`, `Point`, `Order`처럼 자신의 비즈니스 규칙을 가진 객체이다.
coupon.use();
product.decreaseStock();
Repository
DB에서 객체를 조회하거나 저장한다.
`상품 조회`, `쿠폰 조회`, `주문 저장` 등이 여기와 연결된다.
상품 주문 시나리오
사용자가 상품 1개를 주문한다. 사용해야 하는 것은 `Coupon`, `Product`, `Point`, `Order` 네 가지이다.
다음 규칙이 이미 확정되어 있다고 가정한다.
- 사용자는 자신의 미사용 쿠폰만 사용할 수 있다.
- 상품 재고가 있어야 주문할 수 있다.
- 사용자는 결제에 필요한 포인트를 가지고 있어야 한다.
- 모든 조건을 만족하면 주문을 생성한다.
쿠폰은 반드시 사용하는 주문이라고 가정한다.
정상적인 주문의 흐름
Client
|
| 주문 요청
v
Controller
|
| order(...)
v
Application Service
|
|
| ??? 쿠폰 관련 작업
|
| ??? 상품 관련 작업
|
| ??? 포인트 관련 작업
|
| ??? 주문 관련 작업
|
v
Repository
그런데 왜 순서가 중요한가?
예를 들어 `포인트 차감 → 재고 확인 → 재고 없음 발견 → 주문 실패` 와 같은 순서를 상상했을 때,
"주문은 실패했는데 이미 차감한 포인트는?" 질문이 생긴다.
반대로 `재고 차감 → 쿠폰 검사 → 이미 사용된 쿠폰 발견 → 주문 실패`일 경우,
"주문은 실패했는데 재고는 이미 감소했나?" 라는 문제가 생긴다.
검증과 상태 변경을 구분한다
주문 과정에서 하는 작업을 조금 다른 관점으로 나눌 수 있다.
상태를 확인하는 작업
쿠폰을 사용할 수 있는가?
재고가 있는가?
포인트가 충분한가?
현재 상태를 확인한다.
실제 상태를 변경하는 작업
쿠폰 → 사용 상태로 변경
재고 → 1 감소
포인트 → 차감
주문 → 생성
DB 입장에서 보면 Before/After가 생기는 작업이다.
이 두 종류를 구분해서 생각하면 실행 순서를 설계하기 쉬워진다.
그런데 검증부터 전부 하고 변경하면 끝일까?
여기서 동시성 문제가 조금씩 보이기 시작한다.
재고 확인 → 현재 1개 있음
쿠폰 확인 → 정상
포인트 확인 → 충분
...
나중에 재고 차감
그 사이에 다른 사용자가 마지막 재고를 가져갈 수도 있을 것이다.
A: 재고 확인 → 1
B: 재고 확인 → 1
B: 재고 차감 → 0
A: "아까 1이라고 확인했으니까 괜찮겠지?"
예제
A. 쿠폰 검증
B. 쿠폰 사용 처리
C. 재고 확인
D. 재고 차감
E. 포인트 확인
F. 포인트 차감
G. 주문 생성
H. 주문 저장
위와 같은 작업이 있고, 전체적인 호출 구조는 아래와 같다.
Client
↓
Controller
↓
Application Service
↓
Coupon / Product / Point / Order
↓
Repository
정상 주문 순서
A~H를 어떤 순서로 실행하는 것이 자연스러울까?
업무 흐름으로 보면, 보통 아래와 같이 생각하는 편이 자연스럽다.
주문 가능한지 확인 → 실제로 필요한 상태 변경 → 주문 생성 → 주문 저장
C. 재고 확인
→ A. 쿠폰 검증
→ E. 포인트 확인
→ D. 재고 차감
→ B. 쿠폰 사용 처리
→ F. 포인트 차감
→ G. 주문 생성
→ H. 주문 저장
각 지점에서 실패한다면?
재고 확인 ≠ 재고 차감
쿠폰 검증 ≠ 쿠폰 사용 처리
포인트 확인 ≠ 포인트 차감
각각의 차이를 잡아야 한다.
요점
재고 확인 → 상태 변경 X
재고 차감 → 상태 변경 O
쿠폰 검증 → 상태 변경 X
쿠폰 사용 처리 → 상태 변경 O
포인트 확인 → 상태 변경 X
포인트 차감 → 상태 변경 O
'Spring' 카테고리의 다른 글
| [설계] Layered Architecture / Application Service / Repository Interface / DIP (0) | 2026.08.25 |
|---|---|
| [설계] 도메인 모델링 / 객체의 책임 / 응집도 (0) | 2026.08.24 |
| [TDD] 자연어 요구사항을 테스트 케이스로 바꾸기 (0) | 2026.08.21 |
| [TDD] TDD 맛보기 (0) | 2026.08.20 |
| [TDD] 좋은 테스트란 무엇인가 (0) | 2026.08.19 |