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

+ Recent posts