| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 1 | 2 | |||||
| 3 | 4 | 5 | 6 | 7 | 8 | 9 |
| 10 | 11 | 12 | 13 | 14 | 15 | 16 |
| 17 | 18 | 19 | 20 | 21 | 22 | 23 |
| 24 | 25 | 26 | 27 | 28 | 29 | 30 |
| 31 |
- compose
- android designsystem
- 추상 팩토리
- Coroutines
- 함수형프로그래밍
- define
- 팩토리 메소드
- 디자인패턴 #
- 코루틴
- material3
- 프로토타입 패턴
- kotlin multiplatform
- 옵저버 패턴
- factory method
- Kotlin
- kmp
- 코틀린
- Design Pattern
- PrototypePattern
- 디자인패턴
- Functional Programming
- Abstract Factory
- 추상팩토리패턴
- Observer Pattern
- 안드로이드 디자인시스템
- builderPattern
- 빌터패턴
- designPattern
- ㅋㅁ
- 코틀린멀티플랫폼
- Today
- Total
오늘도 더 나은 코드를 작성하였습니까?
도메인 주도 설계 (Domain Driven Design - DDD)의 이벤트 스토밍 개념정리2 본문
왜 이벤트 스토밍을 하는가
1. 도메인 전문가 및 기획, 개발자, QA의 언어 간극을 좁히기 위해
가장 큰 이유입니다. 보통 PM/기획자/현업 담당자가 들고 오는 요구사항 문서는 "사용자가 결제를 하면 주문이 처리된다" 같은 추상적인 문장이에요. 개발자는 이걸 받아서 머릿속으로 "그럼 이건 어떻게 분기하지?", "실패하면?", "동시 요청이면?" 같은 질문을 혼자 만들어내며 구현합니다. 결과적으로 개발자가 비즈니스를 추측하는 코드가 만들어지고, 나중에 "이거 그렇게 동작하면 안 되는데?" 하고 충돌이 발생한다.
이벤트 스토밍은 이 사람들을 한 방에 모아놓고, 벽에 포스트잇을 같이 붙이면서 "결제 요청됨 다음에 뭐가 발생해요?", "그때 PG사가 응답을 안 주면요?" 같은 질문을 그 자리에서 즉시 해결합니다. 추측 대신 합의.
2. 숨겨진 복잡성을 미리 드러내기 위해
요구사항 문서에는 "결제 처리" 한 줄로 끝나지만, 막상 이벤트 스토밍을 하면 빨간 Hotspot이 10개씩 붙습니다. "중복 결제 어떻게 막죠?", "타임아웃 처리는?", "PG사 장애 시 재시도는?" — 이 질문들이 개발 시작 전에 발견되는 게 핵심이에요. 코드를 다 짠 다음에 발견되면 비용이 100배가 됩니다.
3. 시스템 경계(Bounded Context)를 식별하기 위해
벽에 포스트잇을 다 붙이고 나면, 자연스럽게 "여기는 결제 영역", "여기는 주문 영역", "여기는 배송 영역" 처럼 응집된 덩어리가 보입니다. 이 덩어리들이 바로 Bounded Context이고, 마이크로서비스 분리나 모듈 경계의 근거가 됩니다. 추측이 아니라 도메인 자체의 모양을 따라가는 거죠.
4. 빠르고 저렴하다
UML이나 시퀀스 다이어그램을 그리려면 도구 띄우고 박스 위치 잡고 정렬하느라 시간이 가는데, 이벤트 스토밍은 포스트잇과 벽만 있으면 됩니다(요즘은 Miro/FigJam). 반나절~하루 안에 한 도메인이 정리되고, 틀렸으면 떼고 다시 붙이면 끝입니다. 진입 장벽이 낮아서 비개발자도 참여할 수 있어요.
결과물은 무엇인가
1. 도메인 이벤트 카탈로그
가장 직접적인 산출물입니다. 그 도메인에서 실제로 발생하는 모든 사건의 목록이에요. "결제 요청됨", "PG사 응답 수신됨", "결제 완료됨", "주문 확정됨"... 이 목록은 그대로 코드의 sealed class나 이벤트 타입 정의로 옮겨갑니다.
sealed class PaymentEvent {
data class PaymentRequested(val orderId: String, val amount: Long) : PaymentEvent()
data class PgResponseReceived(val result: PgResult) : PaymentEvent()
data class PaymentCompleted(val transactionId: String) : PaymentEvent()
data class PaymentFailed(val reason: String) : PaymentEvent()
}
2. Aggregate 식별과 책임 분배
"이 Command를 받아서 이 Event를 발생시키는 책임자가 누구인가"가 명확해집니다. 결제 Aggregate, 주문 Aggregate, 배송 Aggregate처럼요. 이게 그대로 도메인 모델의 클래스 구조가 됩니다. Android 코드에서는 Repository 또는 UseCase의 경계와 연결된다.
3. Bounded Context 경계
큰 워크샵에서는 색깔이나 영역으로 "여기서부터 여기까지는 결제 컨텍스트", "이 너머는 주문 컨텍스트" 식으로 묶입니다. 이 경계가:
- 모듈 분리 기준 (:feature-payment, :feature-order)
- 팀 분담 기준
- API/이벤트 계약의 경계
4. Hotspot 목록 (미결정 사항 리스트)
빨간 포스트잇들이 그대로 백로그/이슈 트래커로 옮겨갑니다. "PG사 장애 시 재시도 정책 결정 필요", "중복 결제 방지 메커니즘 설계 필요" — 이게 다음 스프린트의 기술 부채 또는 설계 과제가 되죠. 워크샵에서 답이 안 나온 게 부끄러운 게 아니라, 답이 없는 곳을 명시적으로 찍어둔 게 가치입니다.
5. Read Model 명세 (UI 요구사항)
각 Read Model 포스트잇은 그대로 화면 명세가 됩니다. "결제 화면에는 이런 정보가 필요하다", "결제 결과 화면에는 저런 정보가 필요하다" — 이건 UI 디자인과 ViewModel의 StateFlow 구조에 직접 반영돼요.
6. 유비쿼터스 언어 (Ubiquitous Language)
워크샵에서 합의된 단어들 — "결제 요청"인가 "결제 시도"인가, "주문 확정"인가 "주문 완료"인가 — 이게 코드, API, 문서, 회의에서 하나의 통일된 단어로 정착합니다. 더 이상 "기획자는 '결제 시도'라 부르고 백엔드는 'paymentAttempt'이라 부르고 앱은 'PayRequest'라 부르는" 카오스가 없어진다.
정리
이벤트 스토밍은 "사람들 간의 이해 차이"라는 가장 비싼 비용을 미리 지불해서, 나중에 발생할 더 큰 비용을 막는 활동이에요. 산출물은 멋진 다이어그램 한 장이 아니라:
- 코드 구조의 뼈대 (Event, Aggregate, Context)
- 미결정 사항의 명시적 목록 (Hotspot)
- 모두가 같은 단어를 쓰게 만든 사전 (유비쿼터스 언어)
