[BE-52] recruitment DomainService·UseCase·인프라·환불 워커
작업 내용 (설계 의도)
변경 사항
근거 TDD: “서비스 클래스·실패 경로·동시성·멱등·Sequence Diagram 신청/취소”. BE-51 코어 위에 오케스트레이션·영속화·환불 경로를 구현한다.
포함 (전부 신규 파일):
- domain service:
RecruitmentDomainService— create/apply/confirmApplication/cancelPendingApplication/cancelApplication/cancelRecruitment/getRecruitment/findApplications/findApplicationsBy(applicantUserId).findApplicationsBy(applicantUserId): 신청자 본인 기준 신청 목록 조회(FR-4 취소 FE 경로 지탱 — senior-pm 보강).ApplicationRepository.findByApplicantUserId(applicantUserId)추가(도메인 interface + infra QueryDSL).apply: 분산락recruitment:$recruitmentId(booking spinLock 패턴 재사용) +findForUpdateById(비관락) +countActiveByRecruitmentId < capacity위반 시RecruitmentFullException. 정원 충족 시Recruitment.closeWhenFull().cancelApplication:CancellationPolicy.feeRateFor(deadline)→ refundAmount = fee*(1-rate),Application.cancelByApplicant()후ApplicationRefundRequestedEvent적재(플랫폼 귀속, 개설자 정산 없음).cancelRecruitment: 개설자 검증 → CONFIRMED 전원 전액환불 이벤트 + Recruitment CANCELLED.- Repository/Publisher/DistributedLock/CancellationPolicy/RecruitmentRefundGateway 주입(UseCase 직접주입 금지, DomainService만).
- application UseCase (execute 10줄 이내, DomainService만 호출):
CreateRecruitmentUseCase·ApplyRecruitmentUseCase(fee>0이면 createPending+initiatePg, fee0이면 confirmApplication 직접)·CancelApplicationUseCase·CancelRecruitmentUseCase·GetRecruitmentUseCase·ListRecruitmentsUseCase·ListApplicationsUseCase(개설자용)·ListMyApplicationsUseCase(신청자 본인용,findApplicationsBy) + Command/Response(ApplicationResponse: id·recruitmentId·status·paymentId·appliedAt). - infrastructure:
RecruitmentRepositoryImpl·ApplicationRepositoryImpl(+JpaRepository, QueryDSL CustomImpl for findForUpdate/count —@Query금지),RecruitmentRefundGatewayImpl(booking StubPaymentRefundGateway 패턴,@Profile("!prod")). - presentation:
RecruitmentRefundEventWorker(@TransactionalEventListener(AFTER_COMMIT)→ RecruitmentRefundGateway.requestRefund, 예외 catch·에러로그 — booking RefundEventWorker 동형). ApplyRecruitmentUseCase는PaymentDomainService.createPending/initiatePg를OrderType.RECRUITMENT로 호출(CreateBookingUseCase 패턴). OrderType enum·when 분기는 BE-55 소관.
롤백
플래그 OFF(recruitment.enabled) + 역방향 DDL. 환불 실패는 재시도 로그·Operations 알람.
의존
- BE-51 (recruitment 도메인 코어)
다이어그램
처리 흐름 (신청)
sequenceDiagram participant U as ApplyRecruitmentUseCase participant R as RecruitmentDomainService participant P as PaymentDomainService U->>R: apply(recruitmentId, userId) R-->>U: Application(PENDING) alt fee > 0 U->>P: createPending(RECRUITMENT, applicationId, fee) U->>P: initiatePg(...) else fee == 0 U->>R: confirmApplication(applicationId, null) end
클래스 의존
flowchart LR ARU[ApplyRecruitmentUseCase] --> RDS[RecruitmentDomainService] ARU --> PDS[PaymentDomainService] RDS --> Lock[DistributedLock] RDS --> Pol[CancellationPolicy] RDS --> Pub[DomainEventPublisher] Worker[RecruitmentRefundEventWorker] --> Gw[RecruitmentRefundGateway]
테스트 케이스
- 정원 여유 시 신청이 PENDING으로 생성되고 결제 흐름이 시작된다
- 참가비 0원 모집 신청은 PG 없이 즉시 CONFIRMED된다
- 정원이 가득 찬 모집 신청은 RecruitmentFullException(409)이다
- 동시 100건이 마지막 1자리를 경합해도 확정 신청이 정원을 초과하지 않는다(오버부킹 0)
- 마감 5일 전 취소 시 환불액이 참가비의 95%다(수수료 5% 플랫폼 귀속)
- 마감 2일 전 취소 시 환불액이 참가비의 90%다
- 개설자 모집 취소 시 CONFIRMED 전원이 전액환불된다
- 이미 취소된 신청 재취소는 중복 환불 없이 멱등하다
- 환불 게이트가 예외를 던져도 원 취소 트랜잭션은 롤백되지 않고 에러 로그를 남긴다
findApplicationsBy(applicantUserId)가 해당 신청자의 신청만 반환하고 타인 신청은 제외한다- 신청 이력이 없는 사용자의 내 신청 조회는 빈 목록을 정상 반환한다