[BE-08] 배민형 내부 API 소스 어댑터
[폐기 — 2026-07-22] 구현 중 실측 결과
career.woowahan.com/robots.txt가 수집 대상 경로/w1/**를Disallow로 명시함을 확인했습니다. robots.txt 존중 결정에 따라 배민 직접 어댑터를 폐기합니다. 배민 공고는 애그리게이터(원티드·점핏·사람인 등, BE-22~29) 수집으로 발견됩니다(DISCOVERED, 관심 승격 가능). 후행 티켓 영향: BE-10(수집 오케스트레이션)은 배민 어댑터에 의존하지 않습니다(SPI 레지스트리에 WOOWAHAN 어댑터 미등록).JobPlatform.WOOWAHANenum 값은 유지하되 어댑터 구현체는 없습니다.
작업 내용 (설계 의도)
근거 TDD: 20260722-타깃-공고-알림-및-지원-히스토리-tdd.md — “소스별 어댑터 설계”
근거 조사: 20260722-채용소스-조사-브리프.md §2 (실제 호출 검증: GET https://career.woowahan.com/w1/recruits?page=1&size=5 → 200, 총 61건, 인증 불필요)
변경 사항
SPA 뒤의 내부 JSON API를 직접 호출하는 어댑터입니다. 헤드리스 브라우저 없이 수집이 성립함이 조사에서 검증됐습니다(PRD Non-Goals와 정합).
능력 선언(SourceCapabilities):
| 항목 | 값 | 근거 |
|---|---|---|
changeSignalKind | SOURCE_VERSION | recruitVersion 증가 = 수정 (해시 불필요) |
providesDeadline | true | recruitEndDate |
providesStructuredWorkArrangement | false | 근무형태 구조화 필드 없음 → ②(JD 본문)만 가능 |
requiresDetailFetch | 조사 결과에 따라 결정 | 상세 엔드포인트 존재 여부 미검증 (TDD Open Questions #4) |
핵심 설계 의도:
- 마감일 정규화(FR-13)가 이 어댑터의 핵심입니다.
recruitEndDate가9999-*(상시채용 센티널) 또는recruitCloseDate가2999-*이면deadlineAt = null로 통일합니다. 센티널 판정은 연도 임계값(>= 2999)으로 처리해 유사 센티널도 함께 걸러냅니다. 이 값이 도메인에 그대로 흘러가면 “9999년에 마감되는 공고”가 되어 마감일 경과 판정이 영원히 동작하지 않습니다. - 비공식 API 리스크를 전제로 설계합니다 — 사전 통보 없이 스키마가 바뀌므로, 필수 필드(
recruitSeq·recruitName) 누락이 감지되면 그 회차를Failed로 처리해 소스 가드(FR-15)가 발동하게 합니다. 예외를 삼키고 빈 목록을 반환하면 silent failure가 되어 마감 오판정으로 이어집니다. - 페이지네이션 base 미검증(TDD Open Questions #3): 첫 응답의
pageNumber로 0-based/1-based를 판별해 전량 수집하고, 응답의 총 건수와 실제 수집 건수가 불일치하면 회차를Failed로 처리합니다. 부분 수집이 “공고가 사라졌다”로 오인되는 사고를 막습니다. recruitSeq→sourceJobId,recruitName→ 직무 매칭 대상,structuredTags는emptyList().- 상세 엔드포인트 존재 여부는 이 티켓에서 확인합니다. 존재하면
requiresDetailFetch=true로 능력 선언만 바꾸면 되고, 없으면 목록 필드만으로 시작해 근무형태는 ②근거 부재로UNKNOWN이 됩니다. - robots.txt·이용약관 확인 결과를 어댑터 KDoc에 기록합니다(NFR-9).
SPI 정합(BE-02): SourcePlatformAdapter를 구현하며 fetchList(CollectionDescriptor.CompanyBound)를 받습니다(sourceType=COMPANY_BOUND). 외부 호출은 SourceRequestExecutor를 경유합니다.
의존
- BE-02
다이어그램
처리 흐름
sequenceDiagram participant R as JobSourceGatewayImpl participant A as WoowahanJobSourceAdapter participant C as WoowahanCareerClient participant W as career.woowahan.com R->>A: fetchList(descriptor) A->>C: fetchPage(첫 페이지) C->>W: GET /w1/recruits?page=N&size=100 W-->>C: JSON (totalCount, pageNumber, recruits[]) A->>A: pageNumber 로 base 판별 loop 남은 페이지 A->>C: fetchPage(다음) end alt 총 건수 == 수집 건수 A->>A: 9999/2999 센티널 → deadlineAt=null 정규화 A-->>R: Fetched(RawJobPosting[]) else 불일치 또는 필수 필드 누락 A-->>R: Failed(reason) end
클래스 의존
flowchart LR subgraph Domain["domain/posting"] Raw[RawJobPosting] Sig[ChangeSignature.SourceVersion] Outcome[SourceCollectionOutcome] Cap[SourceCapabilities] end subgraph Adapter["infrastructure/posting/collector/woowahan"] Ad[WoowahanJobSourceAdapter] Cl[WoowahanCareerClient] Dto[WoowahanRecruitResponse] Deadline[WoowahanDeadlineNormalizer] end Ad --> Cl Ad --> Deadline Cl --> Dto Deadline --> Raw Ad --> Sig Ad --> Outcome Ad --> Cap
테스트 케이스
- 실제 응답 fixture를 파싱하면
recruitSeq가sourceJobId로 매핑된다 recruitEndDate = 9999-12-31 00:00:00인 공고의deadlineAt이null로 정규화된다recruitEndDate = 2026-08-31 18:00:00인 공고는 실제 마감일로 파싱된다recruitCloseDate = 2999-12-31도 센티널로 판정되어null이 된다recruitVersion값이ChangeSignature.SourceVersion으로 매핑되고, 버전이 증가하면 변경으로 판정된다- 버전이 동일하면 변경 없음으로 판정된다
- 여러 페이지 응답을 전량 수집해 총 건수와 일치시킨다
- 응답 총 건수와 수집 건수가 불일치하면
Failed를 반환한다 - 필수 필드(
recruitSeq)가 누락된 응답은Failed를 반환하고 빈 목록을 반환하지 않는다 structuredTags가 빈 목록으로 반환되어 근무형태 ①근거가 없음을 표현한다- API가 5xx를 반환하면
Failed(reason)을 반환한다 supports(WOOWAHAN)가 true를 반환한다