[BE-26] 원티드 애그리게이터 어댑터 (규약 회색지대)

작업 내용 (설계 의도)

근거 TDD: 20260722-타깃-공고-알림-및-지원-히스토리-tdd.md — “애그리게이터 어댑터 설계 — 규약 회색지대 4종” 근거 조사: 20260722-채용소스-조사-브리프.md “원티드” (실측: GET wanted.co.kr/api/v4/jobs?...&limit=N·/api/chaos/navigation/v1/results?job_group_id=... JSON 무인증, 커서 페이지네이션(links.next), 응답에 company.id·company.name·title·status(active). robots.txt가 CloudFront WAF에 의해 403 → robots 판단 불가, WAF 정책 변경 시 예고 없이 차단 가능)

변경 사항

원티드 공개 JSON API를 검색 조건으로 수집하는 애그리게이터 어댑터입니다. 규약 회색지대(robots 판단 불가)라 규약 준수를 보수적으로 강제하는 것이 최우선입니다.

능력 선언(SourceCapabilities):

항목근거
sourceTypeAGGREGATOR검색 조건(job_group_id)
changeSignalKindFIELD_HASH (기본)변경 감지 필드 미검증 → 실호출로 확정 시 조정
providesDeadline실호출로 확정미검증
companyIdentifierInDetailOnlyfalse목록에 company.id·company.name 존재
supportsIncrementalSincefalse
규약 정책UA 명시·요청 지연·커서 페이지 크기 상한·1일 1회robots WAF 403 → 보수적

핵심 설계 의도:

  • 미검증 항목을 첫 단계에서 실호출로 확정합니다(브리프 방침) — 마감일 필드·변경 감지 필드·페이지네이션 동작. 확정 전까지 changeSignature=FieldHash(제목·회사·마감일 해시)와 보수적 정책을 기본값으로 둡니다. 어댑터 계약은 이미 이를 수용합니다.
  • 회사 식별은 목록에서 확보company.namesourceCompanyName(dedup·자동 등록 입력).
  • 커서 페이지네이션links.next의 offset을 따라 전량 수집. status=active가 아닌 공고 처리 방침을 실호출로 확정합니다.
  • 규약 강제 — robots를 판단할 수 없으므로 소스 고장 감지(FR-19)로 WAF 정책 변경을 포착하고(403 연속 시 3일 내 알림), 모든 호출은 SourceRequestExecutor(UA·지연·상한) 경유. WAF 차단 시 즉시 중단 가능해야 합니다(Operations “원티드 robots WAF” 모니터링).
  • robots·약관·요청 정책 확인 결과와 회색지대 판정 근거를 어댑터 KDoc에 기록(NFR-9·NFR-11).
  • supportedCategories()로 지원 job_group_id↔라벨 목록을 선언(FE 요청 #12).

롤백: job_sources.disabled_at 기록으로 이 소스만 소프트 비활성화.

의존

  • BE-02 (SPI·SourceRequestExecutor·ChangeSignature.FieldHash)

다이어그램

처리 흐름

sequenceDiagram
    participant R as JobSourceGatewayImpl
    participant A as WantedAggregatorAdapter
    participant E as SourceRequestExecutor
    participant W as wanted.co.kr
    R->>A: fetchList(Aggregator descriptor)
    loop 커서 페이지 (links.next · 지연 · UA)
        A->>E: get(허용 경로, cursor)
        E->>W: GET /api/v4/jobs 또는 chaos results
        alt 200
            W-->>E: JSON (company·title·status)
        else 403 (WAF)
            W-->>E: 차단 → Failed (고장 감지 입력)
        end
    end
    A->>A: 필드 해시 · 회사명 추출 · 마감일 정규화(실호출 확정)
    A-->>R: Fetched(RawJobPosting[]) 또는 Failed

클래스 의존

flowchart LR
    subgraph Domain["domain/posting"]
        Raw[RawJobPosting]
        Sig[ChangeSignature.FieldHash]
        Cap[SourceCapabilities]
        Outcome[SourceCollectionOutcome]
    end
    subgraph Adapter["infrastructure/posting/collector/aggregator/wanted"]
        Ad[WantedAggregatorAdapter]
        Dto[WantedJobResponse]
        Hash[FieldHashCalculator]
    end
    subgraph Common["collector/common"]
        Exec[SourceRequestExecutor]
    end
    Ad --> Exec
    Ad --> Dto
    Ad --> Hash
    Hash --> Sig
    Ad --> Cap
    Ad --> Outcome

테스트 케이스

  • 실호출 확정 fixture를 파싱하면 idsourceJobId, company.namesourceCompanyName으로 매핑된다
  • 커서(links.next)를 따라 전량 수집한다
  • 주요 필드가 동일하면 같은 FieldHash로 변경 없음으로 판정된다
  • 마감일 필드가 확정된 뒤 정규화되어 상시채용은 null이 된다
  • 403(WAF 차단) 응답 시 Failed를 반환하고 소스 고장 감지 입력이 된다
  • 403이 3일 연속되면 소스 고장 알림 대상이 된다
  • 요청 사이에 지연이 적용되고 정책 UA가 부착된다
  • supports(WANTED)가 true, sourceTypeAGGREGATOR를 반환한다
  • supportedCategories()job_group_id↔라벨 목록을 반환한다
  • 첫 단계 실호출로 마감일·변경 감지 필드를 확정한 뒤 capability를 조정한다(구현 절차)