[BE-49] 지원 건 담당자 CRUD (FR-80)

작업 내용 (설계 의도)

근거 TDD: 20260808-지원관리-확장-tdd.md — “방안 5 컨텍스트 경계 (담당자 → application 합류)”, “API 계약 1단계 담당자”

변경 사항

FR-80은 담당자를 회사가 아니라 지원 건에 귀속시키라고 명시합니다 — 같은 회사의 다른 공고·다른 전형에 잘못 연결되는 것을 막기 위해서입니다. 지원과 라이프사이클이 완전히 같고 독립 조회 요구가 없으므로 신규 컨텍스트를 만들지 않고 application 컨텍스트에 합류시킵니다.

  1. job_application_contacts 테이블과 JobApplicationContact 엔티티를 추가합니다. 지원 1건에 담당자 N명(1:N).
  2. emailAddress는 소문자로 정규화해 저장합니다 — FR-91의 담당자 일치 20점 축이 발신 이메일 주소로 판정하기 때문입니다. 이 티켓이 그 축의 전제(B-14: 담당자 저장소가 없으면 20점 축이 항상 0점)를 세웁니다.
  3. 신규 파일만 추가합니다JobApplicationContact.kt, JobApplicationContactRepository.kt, ApplicationContactDomainService.kt, ApplicationContactApiController.kt(신규 컨트롤러), UseCase 4종. 기존 ApplicationApiController.kt·ApplicationDomainService.kt를 건드리지 않아 같은 wave의 다른 티켓과 파일이 겹치지 않습니다.
  4. 지원 건 존재 검증(404)은 application 레이어 UseCase가 ApplicationQueryDomainService로 먼저 수행합니다 — DomainService끼리 직접 부르지 않습니다.

의존

  • BE-45 (에러 코드)
  • DB-02 (job_application_contacts)

다이어그램

처리 흐름

sequenceDiagram
    participant FE as web(SPA)
    participant C as ApplicationContactApiController
    participant U as RegisterApplicationContactUseCase
    participant AQ as ApplicationQueryDomainService
    participant CD as ApplicationContactDomainService
    participant R as JobApplicationContactRepository
    FE->>C: POST /api/applications/{id}/contacts
    C->>U: execute(command)
    U->>AQ: getBy(applicationId)
    alt 지원 없음
        AQ-->>U: ApplicationNotFoundException (404)
    else 존재
        U->>CD: register(applicationId, input)
        CD->>R: save(JobApplicationContact)
        CD-->>U: JobApplicationContact
    end
    U-->>C: ApplicationContactResponse
    C-->>FE: 201

클래스 의존

flowchart LR
    subgraph Presentation["presentation/application"]
        Api[ApplicationContactApiController]
    end
    subgraph Application["application/application"]
        Reg[RegisterApplicationContactUseCase]
        List[ListApplicationContactsUseCase]
        Upd[UpdateApplicationContactUseCase]
        Del[DeleteApplicationContactUseCase]
    end
    subgraph Domain["domain/application"]
        CD[ApplicationContactDomainService]
        AQ[ApplicationQueryDomainService]
        Entity[JobApplicationContact]
        Repo[JobApplicationContactRepository]
    end
    Api --> Reg
    Api --> List
    Api --> Upd
    Api --> Del
    Reg --> AQ
    Reg --> CD
    CD --> Entity
    CD --> Repo

테스트 케이스

  • 담당자를 등록하면 201과 생성된 담당자가 반환된다
  • 같은 지원 건에 담당자 2명을 등록하면 목록에 2건이 조회된다
  • 존재하지 않는 지원 건에 등록하면 404 APPLICATION_NOT_FOUND
  • 이름이 비어 있으면 400 VALIDATION_FAILED
  • 이메일을 대문자로 입력해도 소문자로 정규화되어 저장된다
  • 담당자를 수정하면 변경된 값이 반환되고 updatedAt이 갱신된다
  • 다른 지원 건의 담당자 id로 수정을 시도하면 404다 (소유 검증)
  • 담당자를 삭제하면 204이고 목록에서 사라진다
  • 이미 삭제된 담당자를 다시 삭제하면 404다
  • 담당자가 없는 지원 건 조회 시 빈 배열을 반환한다 (0건 경계)