[BE-65] 추천도 조회 API · 관심 공고 일괄 재평가 (FR-96·97)
작업 내용 (설계 의도)
근거 TDD: 20260808-지원관리-확장-tdd.md — “API 계약 2단계 지원 추천도”, “Sequence Diagram 프로필 확정과 관심 공고 재평가”
변경 사항
GET /api/job-postings/{jobPostingId}/recommendation과POST /api/recommendations/re-evaluations를 신설합니다.- 재평가 대상은 관심 공고로 한정합니다(FR-97 B-20) — 관리 상태가
INTERESTED또는PLANNED인 그룹의 대표 공고입니다. “전체 공고 일괄 평가”는 PRD Non-Goals입니다. - 크로스 컨텍스트 조합(application 레이어) — watchlist에서 그룹 id 집합 → posting에서 대표 공고·JD·태그 → matching에서 근무형태 판정 →
RecommendationTarget으로 변환 → recommendation DomainService. 소비 컨텍스트 시그니처에 posting·matching 타입을 두지 않습니다. - 대상별
REQUIRES_NEW트랜잭션 격리 — 1건 실패가 나머지를 막지 않고, 실패는failures[]에 담아 계속 진행합니다. 기존EvaluateJobPostingsUseCase.kt:48-50·:109-118패턴을 그대로 씁니다. - 동기 실행입니다 — NFR-13(관심 공고 200건 5분 이내)이고 규칙 기반이라 외부 호출이 없습니다. 잡 상태 추적 테이블·비동기 워커를 만들지 않습니다.
- 프록시 타임아웃은 BE-56이 nginx
proxy_read_timeout 600s로 해소합니다(C-5). - 서버 측 방어: 재평가 대상이 1,000건을 넘으면 400
RECOMMENDATION_TARGET_LIMIT_EXCEEDED로 거부하고, 응답에actualCount·limit을 실어 FE가 “N건 → 1,000건 이하로 좁혀 주세요”를 조립하게 합니다(A). 상한 없이 열어두면 타임아웃을 아무리 늘려도 언젠가 넘습니다. - 응답 스키마에
elapsedMillis: number(밀리초) 를 포함합니다(B) — 3회 연속 60초를 넘기면 비동기 잡 큐를 재검토합니다(TDD Open Questions #13).
- 프록시 타임아웃은 BE-56이 nginx
- 확정 프로필이 없으면 409
RESUME_PROFILE_NOT_CONFIRMED입니다 — 확정 전 초안은 계산에 쓰이지 않습니다. - Operations 요구 충족 — 응답의
notEvaluableCount·failures[{jobPostingId, reason}]가 PRD Operations “재평가 실패 공고 수와 사유 집계”를 만족합니다. 별도 조회 화면을 만들지 않습니다. - 피처 플래그
recommendation.evaluation이 OFF면 409FEATURE_DISABLED입니다.
파일 소유: RecommendationApiController.kt(신규). BE-62의 ResumeProfileApiController.kt와 별개 파일입니다.
의존
- BE-63 (추천도 계산 도메인)
- BE-50 (관심 공고 그룹 조회 — 1단계 산출물)
다이어그램
처리 흐름
sequenceDiagram participant FE as web(SPA) participant C as RecommendationApiController participant U as EvaluateInterestedPostingsUseCase participant W as WatchStateDomainService participant P as PostingDomainService participant M as MatchResultQueryDomainService participant R as RecommendationDomainService FE->>C: POST /api/recommendations/re-evaluations C->>U: execute(command) U->>R: planEvaluation(force) — 확정 프로필·가중치·revision alt 확정 프로필 없음 R-->>U: ResumeProfileNotConfirmedException (409) end U->>W: findGroupIdsBy([INTERESTED, PLANNED]) U->>P: 그룹 → 대표 공고 + JD + 태그 배치 조회 U->>M: findAllBy(jobPostingIds) 근무형태 배치 조회 loop 대상마다 (REQUIRES_NEW) U->>R: evaluateOne(target, plan) alt 실패 U->>U: failures[]에 추가하고 계속 end end U-->>C: evaluated / skipped / notEvaluable / failures
클래스 의존
flowchart LR subgraph Presentation["presentation/recommendation"] Api[RecommendationApiController] end subgraph Application["application/recommendation"] Eval[EvaluateInterestedPostingsUseCase] Get[GetRecommendationUseCase] Mapper[RecommendationTargetMapper] end subgraph Domain["domain"] RDS[RecommendationDomainService] WDS[WatchStateDomainService] PDS[PostingDomainService] MDS[MatchResultQueryDomainService] Flag[FeatureFlagGateway] end Api --> Eval Api --> Get Eval --> Mapper Eval --> RDS Eval --> WDS Eval --> PDS Eval --> MDS Eval --> Flag
테스트 케이스
- 관심 공고 3건을 재평가하면
evaluatedCount=3이다 - 관리 상태가
EXCLUDED인 그룹의 공고는 재평가 대상이 아니다 - 관리 상태가 없는 공고는 재평가 대상이 아니다 (전체 일괄 평가 미지원)
- 확정 프로필이 없으면 409
RESUME_PROFILE_NOT_CONFIRMED다 - 같은 기준 버전으로 이미 평가된 공고는
force=false면 skip되고skippedCount에 반영된다 force=true면 이미 평가된 공고도 다시 계산된다- JD 없는 공고는
notEvaluableCount에 잡히고failures[]에JD_UNAVAILABLE로 담긴다 - 대상 1건이 예외로 실패해도 나머지가 계속 처리되고
failures[]에 1건이 담긴다 - 관심 공고 200건 재평가가 5분 이내에 완료된다 (NFR-13)
- 추천도 조회 시 축별
judgeable·normalizedWeight·fulfillmentRate·항목 근거가 반환된다 - 평가 결과가 없는 공고를 조회하면 404
RECOMMENDATION_NOT_FOUND다 - 판단 불가 축의
fulfillmentRate가 null로 응답된다 - 대상이 0건이면 즉시 200과 0 카운트를 반환한다 (0건 경계)
- 대상이 1,001건이면 400
RECOMMENDATION_TARGET_LIMIT_EXCEEDED이고actualCount=1001·limit=1000이 응답에 담긴다 (경계값) - 대상이 정확히 1,000건이면 실행된다 (경계값)
- 응답 스키마에
elapsedMillis(밀리초)가 포함되고 실제 소요 시간이 담긴다 - 피처 플래그 OFF면 409
FEATURE_DISABLED다 - 대상별 트랜잭션이 REQUIRES_NEW로 격리되어 1건 롤백이 다른 건에 전파되지 않는다