[BE-51] recruitment 도메인 코어 — Recruitment·Application·CancellationPolicy
작업 내용 (설계 의도)
변경 사항
근거 TDD: “Detail Design 도메인 모델·인터페이스 시그니처·상태 전이 표”.
신규 domain/recruitment 바운디드 컨텍스트의 순수 도메인 코어를 구축한다. 후행 티켓(BE-52 서비스·BE-53 프레젠테이션·BE-55 결제확장)이 참조하는 공통 계약이라 선행 wave 단독 병목으로 확립한다.
포함:
Recruitment(@Entity, private 생성자 + create/reconstitute) — capacity·feeAmount(0 허용)·activityAt·applicationDeadline·communityId(Long? ID참조)·recruiterUserId·status.canApply()·closeWhenFull()·cancelByHost(userId)캡슐화. 다른 도메인 Entity 직접 참조 금지(communityId는 Long).RecruitmentStatus(OPEN/CLOSED/CANCELLED,canTransitTo)Application(@Entity) — recruitmentId·applicantUserId·status·paymentId(Long?).confirm(paymentId)(멱등)·cancelByApplicant()·cancelPending()·markRefunded().ApplicationStatus(PENDING/CONFIRMED/CANCELLED/REFUNDED,canTransitTo)CancellationPolicyinterface +TieredCancellationPolicy(now 내부 해결, no-time-parameter) — 7일초과 0 / 3~7일 0.05 / 3일이내 0.10- 이벤트:
ApplicationRefundRequestedEvent(Layer1, topic=null, refundAmount 포함), 필요 시RecruitmentCreatedEvent - 예외:
RecruitmentFullException(409)·ApplicationCancellationClosedException(422)·NotRecruiterException(403) 등 (BusinessException상속) - Repository interface:
RecruitmentRepository(findForUpdateById 포함)·ApplicationRepository(countActiveByRecruitmentId 등) - Gateway interface:
RecruitmentRefundGateway(booking PaymentRefundGateway와 별개 — R1) - 시간은
ZonedDateTime,!!·@Query·LocalDateTime금지.
롤백
신규 패키지 추가라 롤백은 배포 되돌림(플래그와 무관, 미참조 코드는 무해).
의존
- 없음
다이어그램
클래스 의존
flowchart LR Rec[Recruitment] --> RS[RecruitmentStatus] App[Application] --> AS[ApplicationStatus] Pol[TieredCancellationPolicy] -.->|implements| PolI[CancellationPolicy] RepoR[RecruitmentRepository] RepoA[ApplicationRepository] Gw[RecruitmentRefundGateway]
테스트 케이스
- 정원 여유·미마감 OPEN 모집은
canApply()가 true다 - 정원이 가득 차면
closeWhenFull()로 CLOSED로 전이한다 - CancellationPolicy: 마감 8일 전 취소는 0%, 정확히 7일 전은 0%, 5일 전은 5%, 정확히 3일 전은 10%, 1일 전은 10%다 (경계값)
- 이미 CONFIRMED된 Application에 confirm 재호출 시 no-op으로 멱등하다
- CANCELLED된 Application에 cancelByApplicant 재호출은 상태 가드로 no-op이다
- 마감 이후 cancelByApplicant는 ApplicationCancellationClosedException을 던진다
- 개설자가 아닌 사용자의 cancelByHost는 NotRecruiterException을 던진다
- feeAmount 음수·capacity 0 생성은 검증 예외를 던진다