1. 왜 이 주제를 골랐는가
결재 시스템은 단순 CRUD가 아니라
- 상태 전이
- 동시성
- 중복 요청
- 조직/권한 규칙
이 한꺼번에 얽히는 설계형 도메인이다.
“결재를 기능이 아니라 상태 머신으로 설계할 수 있는가”
를 증명하기 위해 만들었다.
2. 도메인 경계(Bounded Context)
Approval Context
- Approval (결재건)
- ApprovalStep (단계)
- Approver (결재자)
- ApprovalAction (승인/반려 이력)
외부 도메인(정산/회계/알림)은 직접 참조하지 않고 이벤트로만 연결한다.
3. 상태머신 설계
Approval은 명확한 상태 전이를 가진다.
DRAFT → IN_PROGRESS → APPROVED → REJECTED
- 각 Step은 단일 승인 주체
- 다음 단계 활성화는 현재 단계 승인 완료 시점에만 발생
- 상태 전이는 서버에서만 결정 (클라이언트 신뢰 X)
4. 트랜잭션 경계
하나의 승인 요청 = 하나의 로컬 트랜잭션
트랜잭션 안에서 처리하는 것:
- 승인자 권한 검증
- 현재 Step 상태 검증
- 승인 처리
- 다음 Step 활성화
- Outbox 이벤트 저장
트랜잭션 밖:
- 알림 발송
- 외부 시스템 반영
👉 결재의 정합성은 트랜잭션 안에서, 확장은 비동기로
5. 멱등성 설계
결재는 중복 요청이 반드시 발생한다.
멱등 키 전략
approve:{approvalId}:{stepId}:{approverId}:{clientRequestId}
- DB 유니크 인덱스로 중복 차단
- 이미 처리된 요청이면 이전 결과 반환
- 상태 전이는 낙관적 락(version) 으로 보호
6. Outbox 이벤트
승인 완료 시 다음 이벤트를 저장한다.
ApprovalCompletedEvent - approvalId - finalStep - approvedAt
이벤트는 비동기로:
- 알림
- 정산 대상 등록
- 감사 로그 처리
👉 결재 도메인은 외부 실패에 영향받지 않는다
7. 핵심 코드 (요약)
- POST /approvals/{id}/approve
- ApprovalService.approve(...)
- ApprovalStateMachine.transition(...)
- OutboxEventRepository.save(...)
8. 이 설계로 증명하는 것
- 상태 기반 설계
- 트랜잭션 경계 분리
- 멱등성/동시성 대응
- 장애 전파 차단
LIST
'Architecture' 카테고리의 다른 글
| 동시성 모델 (Virtual Thread vs Executor) (0) | 2026.02.12 |
|---|---|
| Settlement(정산 배치) 설계 — JobRun·Item·재처리 중심 (0) | 2026.02.11 |
| 정답 없는 환경에서 망하지 않는 시스템을 설계하는 법 (0) | 2026.02.10 |
| MSA와 MSA 아키텍처 (0) | 2026.02.10 |
| 경영학이 왜 개발자/아키텍트와 궁합이 좋은가 (0) | 2026.02.10 |
