[BE-04] application 도메인 모델 — 지원 상태 전이 · 면접 회차 · 히스토리
작업 내용 (설계 의도)
근거 TDD: 20260722-타깃-공고-알림-및-지원-히스토리-tdd.md — “상태 전이 표 / Application”
변경 사항
지원 기록을 공고와 별개 개념으로 분리해 소유합니다. “미지원”은 상태값이 아니라 Application 레코드가 존재하지 않는 것입니다(FR-42) — job_applications에 unique(job_posting_id)를 걸어 공고당 지원 1건을 강제합니다.
핵심 설계 의도:
- 상태 전이는
ApplicationStatusenum 내부canTransitTo()로 캡슐화합니다. 서비스에if (status == X && next == Y)분기를 두지 않습니다. 종료 상태 4종(ACCEPTED/REJECTED/WITHDRAWN/OFFER_DECLINED)은isTerminal()이 true이며 어떤 전이도 거부합니다(FR-43). OFFERED의 허용 전이는ACCEPTED·OFFER_DECLINED2개뿐입니다. 처우협의 단계의 포기는WITHDRAWN이 아니라OFFER_DECLINED로 표현합니다(FR-44).WITHDRAWN은APPLIED·DOCUMENT_SCREENING·INTERVIEWING3단계에서만 가능합니다(FR-45).REJECTED전이 시 탈락 시점 단계를 함께 기록합니다 —Application.rejectedAtStage = 이전 상태.- 면접 회차는 상태값이 아니라 별도 엔티티입니다(FR-46).
Interview(roundNumber, label, scheduledAt, interviewResult, memo)이며 한 지원에 여러 회차를 가집니다.unique(application_id, round_number)로 회차 중복을 막습니다. 레이블은 사용자 지정 문자열입니다(“1차 기술면접” 등). ApplicationStatusHistory가 히스토리 조회의 SSOT입니다(FR-47). 전이가 발생하면Application.transitTo()가 이력 객체를 반환하고, 도메인 서비스가 저장합니다. 전이 없이 이력만 생기거나 그 반대가 되는 경로를 만들지 않습니다.- 생성 이력 규칙(확정): 지원 기록 생성 시
(previousStatus=null → nextStatus=APPLIED)이력 1건을 함께 적재합니다.Application.create(cmd)가Pair<Application, ApplicationStatusHistory>를 반환해 “지원 생성 = 이력 1건”이 도메인에서 강제되게 합니다. 이렇게 해야 히스토리가 지원 시작 시점부터 완결되고 FE 타임라인이 분기 없이 이력만으로 렌더링됩니다.previousStatus가null인 경우는 생성 이력뿐입니다. 따라서 이력 건수는 항상1(생성) + 전이 횟수입니다. allowedNextStatuses()를 도메인이 노출합니다 —ApplicationStatus.allowedNextStatuses(): Set<ApplicationStatus>. 전이 규칙의 SSOT를 BE에 유지하면서 FE가 선택지를 서버에서 받아갈 수 있게 하기 위함입니다(FE 계약 요청 #7 수용). 종료 상태는 빈 집합을 반환합니다.- 지원 이력은 공고가 CLOSED돼도 무기한 보존합니다(NFR-5, FR-18) — 삭제 API를 만들지 않습니다.
- 배치와 사용자 API가 동시에 수정할 여지에 대비해
@Version낙관적 잠금을 둡니다.
범위: 도메인 POJO + 상태 enum + Repository interface + JPA 엔티티/RepositoryImpl(Testcontainers 통합 테스트 포함). UseCase·API는 BE-14·BE-15에서 처리합니다.
테이블 명명: 도메인 클래스는 Application·ApplicationStatusHistory·Interview를 유지하되, 매핑 테이블은 job_applications / job_application_status_histories / job_application_interviews 입니다(DB 컨벤션이 applications 단독 명사를 금지 예시로 지목). 컬럼 상세는 20260722-공고알림앱-design-db.md가 SSOT입니다.
의존
- BE-01
다이어그램
처리 흐름
sequenceDiagram participant C as 호출부(도메인 서비스) participant A as Application participant S as ApplicationStatus participant H as ApplicationStatusHistory participant I as Interview C->>A: transitTo(next, memo) A->>S: canTransitTo(next) alt 전이 불가 S-->>A: false A-->>C: 도메인 예외 (이력 미생성) else 전이 가능 S-->>A: true A->>A: status 갱신 · REJECTED면 rejectedAtStage 기록 A->>H: of(previous, next, memo) A-->>C: ApplicationStatusHistory end C->>I: create(round, label, scheduledAt)
클래스 의존
flowchart LR subgraph Domain["domain/application"] App[Application] Status[ApplicationStatus] History[ApplicationStatusHistory] Interview[Interview] Result[InterviewResult] AppRepo[ApplicationRepository] IntRepo[InterviewRepository] end subgraph Infra["infrastructure/application"] AppImpl[ApplicationRepositoryImpl] IntImpl[InterviewRepositoryImpl] end App --> Status App --> History Interview --> Result AppImpl -.->|implements| AppRepo IntImpl -.->|implements| IntRepo
테스트 케이스
- 지원 기록을 생성하면
(null → APPLIED)생성 이력 1건이 함께 만들어진다 APPLIED→DOCUMENT_SCREENING전이가 허용되고 이력이 총 2건(생성 1 + 전이 1)이 된다APPLIED→INTERVIEWING전이는 단계 건너뛰기로 거부된다APPLIED→DOCUMENT_SCREENING→INTERVIEWING→OFFERED→ACCEPTED전체 경로가 성공하고 이력이 총 5건(생성 1 + 전이 4) 순서대로 쌓인다previousStatus가 null인 이력은 생성 이력 1건뿐이다OFFERED→WITHDRAWN전이는 거부된다 (FR-44)OFFERED→OFFER_DECLINED전이는 허용된다INTERVIEWING→WITHDRAWN전이는 허용된다 (FR-45)- 종료 상태 4종 각각에서 모든 전이 시도가 거부되고 이력이 생성되지 않는다
allowedNextStatuses()가APPLIED에서 3종,OFFERED에서 2종, 종료 상태에서 빈 집합을 반환한다DOCUMENT_SCREENING→REJECTED전이 시rejectedAtStage가DOCUMENT_SCREENING으로 기록된다- 같은 상태로의 전이는 거부된다
- 한 지원에 1차·2차 면접 회차를 추가하면 2건이 조회되고, 같은 회차 번호 재등록은 유니크 제약으로 거부된다
- 면접 결과를
PASSED로 기록하면 지원 상태는 자동 전이되지 않는다(면접 회차와 지원 상태는 독립) - Testcontainers: 같은 공고에 지원 기록을 두 번 생성하면 유니크 제약으로 거부된다
- 공고가 CLOSED여도 지원 이력 조회가 정상 동작한다