[BE-54] 운영 조회 확장(웹훅 수신·터널 상태) · platform/dedup_key 백필 API
작업 내용 (설계 의도)
근거 TDD: 20260808-지원관리-확장-tdd.md — “Observability”, “Release Scenario 1단계 데이터 마이그레이션 계획”
변경 사항
job_postings.platform·MANUALdedup_key백필 API —POST /api/operations/backfills/posting-platform.- Flyway 인라인 백필 DML 금지가 전제입니다. Flyway는 DDL만 하고 값 채우기는 이 배치가 합니다. 대용량 테이블에 단일
UPDATE를 걸면 테이블 전체 락으로 배포가 곧 장애입니다. - Spring Batch를 도입하지 않습니다 — 기존
EvaluateJobPostingsUseCase.kt:83-88의 500건 페이지 순회 + 페이지마다 커밋 패턴을 재사용합니다. 청크 커밋·락 범위 축소·멱등이라는 5단계 절차의 취지를 이 패턴이 이미 충족하고, 일생 1회 백필에 메타 테이블 6개는 과합니다. - 대상은
platform IS NULL AND posting_origin='COLLECTED'또는dedup_key IS NULL AND posting_origin='MANUAL'입니다. 채워진 행은 대상에서 빠지므로 재실행 멱등입니다. platform은job_source_id로 company DomainService에서 배치 조회해 채웁니다(크로스 컨텍스트 조합은 application 레이어).
- Flyway 인라인 백필 DML 금지가 전제입니다. Flyway는 DDL만 하고 값 채우기는 이 배치가 합니다. 대용량 테이블에 단일
- 웹훅 수신 이력 조회 —
GET /api/operations/webhook-receipts(PRD Operations “수신 건수·서명 검증 실패 건수·사유”). - 터널 상태 확인 —
GET /api/operations/tunnel-status(PRD Operations “터널이 끊기면 웹훅 수신 자체가 불가능하므로 별도 확인 수단”). 설정된 공개 호스트명으로GET /api/health-probe를 1회 호출(타임아웃 3초)해 왕복 가능 여부를 판정합니다. 호스트명 미설정이면reachable=false. /api/health-probe— 인증 제외 경로에 이미 포함된 웹훅 경로와 별개로, 이 경로만 추가로 인증 제외합니다. 응답은204이며 어떤 정보도 노출하지 않습니다.
파일 소유: OperationApiController.kt에 엔드포인트 3개를 추가합니다. 같은 wave에 이 파일을 건드리는 티켓이 없습니다.
롤백: 백필은 되돌릴 필요가 없습니다(신규 nullable 컬럼을 채울 뿐이며 아직 아무도 읽지 않습니다). 조회 API는 읽기 전용입니다.
의존
- BE-47 (웹훅 수신 이력 테이블)
- BE-48 (
platform컬럼·createManualdedupKey 계산)
다이어그램
처리 흐름
sequenceDiagram participant Op as 운영자 participant C as OperationApiController participant U as BackfillPostingPlatformUseCase participant P as PostingDomainService participant Co as CompanyDomainService Op->>C: POST /api/operations/backfills/posting-platform C->>U: execute() loop 500건 페이지 U->>P: findAllBackfillTargets(page, 500) alt 빈 페이지 P-->>U: [] → 순회 종료 else 대상 존재 U->>Co: findAllJobSourcesBy(ids) 배치 1회 U->>P: saveAll(platform·dedupKey 채운 공고) end end U-->>C: processedCount / remainingNullCount
클래스 의존
flowchart LR subgraph Presentation["presentation/operation"] Api[OperationApiController] Probe[HealthProbeApiController] end subgraph Application["application/operation"] Backfill[BackfillPostingPlatformUseCase] Receipts[ListWebhookReceiptsUseCase] Tunnel[CheckTunnelStatusUseCase] end subgraph Domain["domain"] PD[PostingDomainService] CD[CompanyDomainService] WD[WebhookReceiptQueryDomainService] TG[TunnelProbeGateway] end Api --> Backfill Api --> Receipts Api --> Tunnel Backfill --> PD Backfill --> CD Receipts --> WD Tunnel --> TG
테스트 케이스
- 백필 실행 후
platform IS NULL AND posting_origin='COLLECTED'건수가 0이 된다 - 백필 실행 후 MANUAL 공고의
dedup_key가 정규화 회사명+제목으로 채워진다 - 백필을 두 번 실행하면 두 번째는 처리 건수가 0이다 (멱등)
- 백필 도중 중단 후 재실행하면 남은 대상만 처리한다
- 백필이 페이지마다 커밋해 단일 트랜잭션으로 전체를 잠그지 않는다
- 대상이 0건이면 즉시 종료하고 200을 반환한다 (0건 경계)
- 백필이
job_sources조회를 페이지당 1회만 수행한다 (N+1 방지) - 웹훅 수신 이력을
result=SIGNATURE_MISMATCH로 필터해 조회할 수 있다 - 웹훅 수신 이력이
days범위 밖이면 조회되지 않는다 days=31이면 400BAD_REQUEST다 (경계값)- 터널 호스트명이 설정되지 않으면
reachable=false, publicHostname=null이다 - 터널 호스트가 응답하지 않으면 3초 타임아웃 후
reachable=false를 반환하고 예외를 던지지 않는다 /api/health-probe가 인증 없이 204를 반환하고 본문이 비어 있다