[BE-56] 단계 2 공통 계약 — 에러 코드·문서 의존성·저장 루트 설정·볼륨 마운트
작업 내용 (설계 의도)
근거 TDD: 20260808-지원관리-확장-tdd.md — “신규 에러 코드”, “Release Scenario 2단계”
변경 사항
단계 2의 유일한 병목입니다. 후행 4개 티켓이 참조하는 공통 산출물을 한 wave에 확정해 GlobalExceptionHandler.kt·build.gradle.kts·application.yml·docker-compose.yml 네 파일의 머지 충돌을 원천 차단합니다.
- 신규 예외·에러 코드 —
DocumentSeriesNotFoundException·DocumentVersionNotFoundException·DocumentExtensionNotAllowedException·DocumentSizeExceededException·DocumentPathEscapeException·DocumentAlreadySubmittedException(domain/document·domain/application),ResumeProfileNotConfirmedException·InvalidRecommendationWeightException·RecommendationNotFoundException(domain/recommendation). 정의 +GlobalExceptionHandler매핑을 함께 합니다. - 빌드 의존성 —
org.apache.pdfbox:pdfbox(PDF 텍스트 추출),org.apache.poi:poi-ooxml(DOCX).hwp는 신뢰할 만한 자바 파서가 없어 추출을 지원하지 않습니다 — 업로드·보관만 하고 프로필 초안은extractionFailed: true로 수동 입력을 안내합니다. - 설정 —
recruitment.document.root-path(환경 변수DOCUMENT_ROOT),spring.servlet.multipart.max-file-size: 20MB·max-request-size: 21MB. 저장 루트를 설정값으로 추상화하는 것은 FR-84 요구입니다. - docker compose 볼륨 마운트 — 호스트
/Users/biuea/Desktop/dpdpdndn/private/이직/지원서류를 컨테이너 내부 경로에 마운트합니다(dev·prod 양쪽). FR-84가 “컨테이너에는 볼륨 마운트로 노출”을 명시했고, 마운트가 없으면 호스트 절대 경로가 컨테이너 안에서 도달 불가입니다. 4-1. nginx 프록시 타임아웃 상향 (C-5) —web/nginx.conf:10-15의/api/location에proxy_read_timeout 600s·proxy_send_timeout 600s·proxy_connect_timeout 5s를 추가합니다.- 문제: 현재
proxy_read_timeout이 없어 nginx 기본값 60초가 적용되는데, NFR-13은 관심 공고 200건 재평가에 5분을 허용합니다. 동기 API로 두면 502가 납니다. 같은 노출이POST /api/resume-profiles/{id}/confirmations(확정 시 동기 재평가)에도 있습니다. - 비동기 잡 큐 미채택 — 잡 상태 테이블·폴링 엔드포인트·FE 폴링이 따라오는데, 200건 × 규칙 기반 계산(외부 호출 0회)은 초 단위라 그 비용을 정당화하지 못합니다. NFR-8(브로커·워커 미도입) 취지와도 어긋납니다.
web/nginx.conf는 이 티켓이 단독 소유합니다 — FE 티켓은 이 파일을 건드리지 않습니다(Single Writer). senior-fe에 전달된 사항입니다.- 단일 사용자 환경이라 워커 고갈 위험이 없어 경로별 location 분리를 하지 않습니다.
- 문제: 현재
- 피처 플래그 시드 2행 —
document.upload·recommendation.evaluation전부enabled=0. FeatureDisabledException+ 409FEATURE_DISABLED는 여기서 정의하지 않습니다 —watchlist.management(단계 1) 때문에 BE-45가 이미 정의·매핑합니다. 최초 분해에서 이 티켓에 둔 것은 순서 오류였습니다. 이 티켓은 단계 2 플래그(document.upload·recommendation.evaluation)가 그 예외를 사용하기만 합니다.
롤백: 볼륨 마운트 제거 + 의존성 되돌리기. 플래그 2행 DELETE.
의존
- DB-03 (단계 2 스키마 마이그레이션 — senior-dba 몫)
다이어그램
처리 흐름
sequenceDiagram participant C as DocumentApiController participant D as DocumentDomainService participant H as GlobalExceptionHandler participant FE as web(SPA) C->>D: upload(input) D-->>C: DocumentSizeExceededException C->>H: 예외 전파 H->>H: 예외 → (400, DOCUMENT_SIZE_EXCEEDED) H-->>FE: 400 { code, message }
클래스 의존
flowchart LR subgraph Config["config"] Handler[GlobalExceptionHandler] end subgraph Domain["domain 신규 예외"] Ext[DocumentExtensionNotAllowedException] Size[DocumentSizeExceededException] Escape[DocumentPathEscapeException] Profile[ResumeProfileNotConfirmedException] Weight[InvalidRecommendationWeightException] Disabled[FeatureDisabledException] end Handler --> Ext Handler --> Size Handler --> Escape Handler --> Profile Handler --> Weight Handler --> Disabled
테스트 케이스
DocumentExtensionNotAllowedException이 400DOCUMENT_EXTENSION_NOT_ALLOWED로 매핑된다DocumentSizeExceededException이 400DOCUMENT_SIZE_EXCEEDED로 매핑된다DocumentPathEscapeException이 400DOCUMENT_PATH_ESCAPE로 매핑된다DocumentAlreadySubmittedException이 409DOCUMENT_ALREADY_SUBMITTED로 매핑된다ResumeProfileNotConfirmedException이 409RESUME_PROFILE_NOT_CONFIRMED로 매핑된다InvalidRecommendationWeightException이 400RECOMMENDATION_WEIGHT_INVALID로 매핑된다RecommendationTargetLimitExceededException이 400RECOMMENDATION_TARGET_LIMIT_EXCEEDED로 매핑되고actualCount·limit이 채워진다- BE-45가 정의한
FeatureDisabledException이 단계 2 플래그 경로에서도 409로 매핑된다 (중복 정의 없음) - 20MB를 넘는 멀티파트 요청이 서블릿 단에서 거부되고 400으로 매핑된다
recruitment.document.root-path가 설정에서 바인딩된다- 기존 예외 매핑이 변경되지 않는다 (회귀)
- 피처 플래그 2행이
enabled=0으로 시드된다 - nginx
/api/location에proxy_read_timeout 600s가 적용되어 60초를 넘는 응답이 502로 끊기지 않는다 - nginx 변경 후 기존 API 프록시 동작(헤더 전달·SPA fallback)이 그대로다 (회귀)