복잡한 설계, 한 번에 이해하기 - 클래스 다이어그램과 시퀀스 다이어그램으로 요구사항 명세 작성하기
요구사항만으로는 부족했다
새로운 서비스 설계 과정에서 요구사항만 정리하면 충분하다고 생각했습니다.
1
2
3
4
- 사용자는 상품을 주문할 수 있다
- 주문 시 재고가 차감된다
- 쿠폰이 있으면 할인이 적용된다
- 포인트로 일부 금액을 결제할 수 있다
문서로 정리하고 바로 구현에 들어갔습니다. 그런데 구현 단계에서 문제가 생겼습니다.
- 구조와 역할이 불명확해졌습니다:
OrderService가 어디까지 담당해야 하는가?OrderFacade와의 경계는? - 데이터 흐름이 혼란스러워졌습니다: 할인 금액은 어디서 계산되고 어디로 전달되는가?
- 협력 관계가 모호해졌습니다:
Coupon은Order를 알아야 하는가,Order가Coupon을 알아야 하는가?
요구사항은 “무엇을” 설명하지만, 다이어그램은 “어떻게”를 명확하게 합니다.
클래스 다이어그램: 정적 구조 설계
클래스 다이어그램은 시스템의 정적 구조를 보여줍니다. 클래스 간의 관계, 속성, 메서드를 시각화합니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
+------------------+ +------------------+
| Order | | OrderItem |
+------------------+ +------------------+
| - id: Long |1 * | - id: Long |
| - userId: Long |------>| - productId: Long|
| - status: Status | | - quantity: int |
| - totalAmount | | - price: Money |
+------------------+ +------------------+
|
| uses
v
+------------------+ +------------------+
| OrderFacade |------>| ProductService |
+------------------+ +------------------+
| + order(cmd) |------>| CouponService |
+------------------+ +------------------+
------>| PointService |
+------------------+
------>| OrderService |
+------------------+
클래스 다이어그램을 그리면서 발견한 것들입니다.
Order와OrderItem의 관계는 1:N입니다.OrderItem은Order없이 존재할 수 없습니다.OrderFacade는 4개의 서비스에 의존합니다. 이건 의도된 설계입니다 (Facade 패턴).- 각 서비스는 자신의 도메인 객체만 다룹니다.
시퀀스 다이어그램: 동적 흐름 시각화
시퀀스 다이어그램은 시간 순서에 따른 객체 간 메시지 교환을 보여줍니다. “주문 생성” 흐름을 시퀀스 다이어그램으로 그려보면 이렇습니다.
1
2
3
4
5
6
7
8
9
10
11
12
Client OrderFacade ProductService CouponService PointService OrderService
| | | | | |
|--order(cmd)---->| | | | |
| |--deductStock--->| | | |
| |<--ok------------| | | |
| |--apply(couponId)---------------->| | |
| |<--discountAmount-----------------| | |
| |--deduct(userId, point)-------------------------->| |
| |<--ok--------------------------------------------| |
| |--create(cmd, discount)----------------------------------------->|
| |<--OrderResult---------------------------------------------------|
|<--OrderResult---|
시퀀스 다이어그램을 그리면서 발견한 것들입니다.
- 흐름이 명확해졌습니다. 재고 -> 쿠폰 -> 포인트 -> 주문 생성 순서입니다.
- 각 서비스에 전달되는 인자와 반환값이 명확해졌습니다.
- 실패 케이스를 어디서 처리해야 하는지가 보였습니다. (재고 부족 예외는
ProductService에서)
다이어그램이 바꾼 것들
다이어그램을 먼저 그리고 구현에 들어가니 달라진 점이 있었습니다.
구현 속도가 빨라졌습니다. 구조가 머릿속에 있으니 코드를 작성할 때 망설임이 없었습니다.
리뷰가 쉬워졌습니다. “이 메서드가 여기 있는 게 맞나요?”라는 질문을 코드 대신 다이어그램으로 논의할 수 있었습니다.
예외 케이스가 보였습니다. 시퀀스 다이어그램을 그리다 보면 “이 단계에서 실패하면 어떻게 되지?”라는 질문이 자연스럽게 생깁니다.
정리
“구조를 시각적으로 정의하는 것이 클린 코드의 출발점”이라는 걸 이번 작업에서 체감했습니다.
요구사항은 “무엇을 만들 것인가”를 답하고, 클래스 다이어그램은 “어떤 구조로 만들 것인가”를 답하고, 시퀀스 다이어그램은 “어떤 순서로 동작할 것인가”를 답합니다. 세 가지가 모두 갖춰져야 구현이 흔들리지 않습니다.
처음부터 완벽한 다이어그램을 그릴 필요는 없습니다. 핵심 흐름만 그려도 충분합니다. 중요한 건 구현 전에 구조를 명확히 하는 습관입니다.