[BE-76] 연락 검토 요청 알림 (FR-94)

작업 내용 (설계 의도)

근거 TDD: 20260808-지원관리-확장-tdd.md — “API 계약 3단계”, “방안 11(멱등 키 확장)“

변경 사항

  1. “연락 검토 요청” 알림을 신설합니다 — 후보 계산이 끝난 연락 이벤트를 사용자에게 알리고, 검토 화면으로 이동하는 링크를 포함합니다(FR-94).
  2. 멱등 키는 기존 규칙을 그대로 확장합니다(FR-94 B-27) — CONTACT_EVENT:{contactEventId}:CONTACT_REVIEW_REQUEST:1. dispatch_sequence는 항상 1(1회성 발송)입니다.
  3. 링크는 터널 공개 호스트명 기준입니다(FR-71·94 A-3) — recruitment.contact.review-base-url 설정값 + 이벤트 id. 사용자가 외출 중에도 알림을 받아 즉시 처리할 수 있어야 한다는 것이 이 알림의 존재 이유입니다.
  4. 발송 시점은 09:00 배치가 아니라 후보 계산 직후입니다 — 연락은 실시간성이 요구됩니다(면접 일정 회신 등). Layer 1 리스너(BE-75)가 후보 계산 후 곧바로 발송 UseCase를 호출합니다.
  5. 메시지는 발신자·제목·최상위 후보 요약·링크를 300자 이내로 조립합니다(message_summary VARCHAR(300) 상한).
  6. 파싱 실패·후보 0건이어도 발송합니다 — 사용자가 원문을 보고 판단해야 하기 때문입니다.
  7. 피처 플래그 notification.contact-review-request가 OFF면 발송하지 않습니다(이벤트는 PENDING으로 남아 검토 화면에서 확인 가능).

롤백: 플래그 OFF.

의존

  • BE-68 (NotificationType.CONTACT_REVIEW_REQUEST·TargetType.CONTACT_EVENT enum·플래그)
  • BE-75 (후보 계산 — 리스너에서 호출)

다이어그램

처리 흐름

sequenceDiagram
    participant L as ContactEventReceivedListener
    participant A as AnalyzeContactEventUseCase
    participant N as SendContactReviewNotificationUseCase
    participant D as NotificationDispatchDomainService
    participant G as DiscordWebhookGateway
    L->>A: 후보 계산 완료
    A->>N: execute(contactEventId)
    N->>D: dispatchOne(ContactReviewTarget)
    D->>D: 멱등 키 CONTACT_EVENT:{id}:CONTACT_REVIEW_REQUEST:1
    alt 이미 발송
        D-->>N: SKIPPED
    else 미발송
        D->>G: send(검토 링크 포함 메시지)
        G-->>D: Delivered / Rejected (백오프 3회)
        D-->>N: SENT / FAILED
    end

클래스 의존

flowchart LR
    subgraph Application["application/notification"]
        UC[SendContactReviewNotificationUseCase]
    end
    subgraph Domain["domain/notification"]
        DS[NotificationDispatchDomainService]
        Target[ContactReviewTarget]
        Key[IdempotencyKey]
        Gateway[DiscordWebhookGateway]
        Flag[FeatureFlagGateway]
    end
    UC --> DS
    DS --> Target
    DS --> Key
    DS --> Gateway
    DS --> Flag

테스트 케이스

  • 후보 계산이 끝나면 검토 요청 알림이 1건 발송된다
  • 메시지에 검토 화면 링크가 포함되고 터널 공개 호스트명 기준이다
  • 같은 이벤트에 대해 두 번 발송을 시도하면 두 번째는 SKIPPED다 (멱등)
  • 후보가 0건이어도 알림이 발송된다
  • 파싱이 실패해도 알림이 발송된다
  • 최상위 후보의 회사명이 메시지 요약에 포함된다
  • 메시지 요약이 300자를 넘지 않는다 (컬럼 상한)
  • 발송 실패 시 idempotency_key가 NULL로 남는다
  • 발송이 5xx면 지수 백오프로 최대 3회 재시도한다
  • 피처 플래그 OFF면 발송하지 않고 이벤트는 PENDING으로 남는다
  • 알림 발송 실패가 연락 이벤트 저장을 롤백하지 않는다
  • 기존 알림 3종 동작이 변경되지 않는다 (회귀)