[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):
| 항목 | 값 | 근거 |
|---|---|---|
sourceType | AGGREGATOR | 검색 조건(job_group_id) |
changeSignalKind | FIELD_HASH (기본) | 변경 감지 필드 미검증 → 실호출로 확정 시 조정 |
providesDeadline | 실호출로 확정 | 미검증 |
companyIdentifierInDetailOnly | false | 목록에 company.id·company.name 존재 |
supportsIncrementalSince | false | — |
| 규약 정책 | UA 명시·요청 지연·커서 페이지 크기 상한·1일 1회 | robots WAF 403 → 보수적 |
핵심 설계 의도:
- 미검증 항목을 첫 단계에서 실호출로 확정합니다(브리프 방침) — 마감일 필드·변경 감지 필드·페이지네이션 동작. 확정 전까지
changeSignature=FieldHash(제목·회사·마감일 해시)와 보수적 정책을 기본값으로 둡니다. 어댑터 계약은 이미 이를 수용합니다. - 회사 식별은 목록에서 확보 —
company.name→sourceCompanyName(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를 파싱하면
id가sourceJobId,company.name이sourceCompanyName으로 매핑된다 - 커서(
links.next)를 따라 전량 수집한다 - 주요 필드가 동일하면 같은
FieldHash로 변경 없음으로 판정된다 - 마감일 필드가 확정된 뒤 정규화되어 상시채용은
null이 된다 - 403(WAF 차단) 응답 시
Failed를 반환하고 소스 고장 감지 입력이 된다 - 403이 3일 연속되면 소스 고장 알림 대상이 된다
- 요청 사이에 지연이 적용되고 정책 UA가 부착된다
supports(WANTED)가 true,sourceType이AGGREGATOR를 반환한다supportedCategories()가job_group_id↔라벨 목록을 반환한다- 첫 단계 실호출로 마감일·변경 감지 필드를 확정한 뒤 capability를 조정한다(구현 절차)