근거 PRD: /Users/biuea/Desktop/dpdpdndn/프로젝트/공고알림앱/20260722-타깃-공고-알림-및-지원-히스토리-prd.md (FR-60 소스 유형 2종)
근거 티켓: tickets/BE-30-followup-jobkorea-dual-source-type.md
근거 조사: 20260722-채용소스-조사-브리프.md “우리은행 — 인크루트 + 잡코리아” + 본 문서 robots 실측(2026-07-27)
원 요구사항은 “우리은행 = 인크루트 + 잡코리아”(한 회사가 복수 소스, Company:JobSource = 1:N)입니다. 그런데 JobPlatform.JOBKOREA가 SourceType.AGGREGATOR로 고정돼, 우리은행의 잡코리아 채용대행(jrs.jobkorea.co.kr/wooribank)을 회사 종속형(COMPANY_BOUND) 소스로 등록할 수 없습니다. BE-21 기동 검증 케이스 6이 이 이유로 400 실패했습니다.
Overview
무엇을: JobPlatform이 소스 유형을 1개로 고정하던 것을 지원 집합(Set<SourceType>) 으로 완화해, JOBKOREA가 AGGREGATOR·COMPANY_BOUND를 겸용하게 합니다.
왜: 한 플랫폼(잡코리아)이 전역 검색형(애그리게이터, www.jobkorea.co.kr)과 회사 채용관형(회사 종속, jrs.jobkorea.co.kr/{slug}) 두 성격을 실제로 겸함. 현재 모델은 이를 표현 못 해 등록 자체가 막힙니다.
어떻게: JobPlatform.supportedSourceTypes: Set<SourceType> + supports(sourceType) 도입, JobSource.forCompany/forAggregator의 require(platform.sourceType == X)를 require(platform.supports(X))로 교체. 스키마 변경 없음(행 단위 source_type 컬럼·유니크 제약이 이미 겸용을 수용). 어댑터 계약(BE-02, 미구현)의 디스패치·능력 조회는 platform 단독 키에서 (platform, sourceType) 키로 일반화해 겸용을 지원.
Terminology
용어
정의
SourceType
채용 데이터 출처 유형 — COMPANY_BOUND(회사 slug 수집) / AGGREGATOR(검색 조건 수집), FR-60
JobPlatform
어댑터 구현 단위 플랫폼(GREENHOUSE·INCRUIT·JOBKOREA 등). domain.common
겸용 플랫폼
하나의 JobPlatform이 두 SourceType을 모두 지원하는 경우 (JOBKOREA)
회사 채용대행(JRS)
잡코리아 Job Recruiting Service — jrs.jobkorea.co.kr/{slug}, 회사별 호스팅 채용관
애그리게이터 잡코리아
www.jobkorea.co.kr 직무 카테고리 전역 검색 (BE-28)
SPI 디스패치
JobSourceGatewayImpl이 요청을 담당 어댑터로 라우팅하는 방식
Define Problem
AS-IS
실제 코드 인용:
domain/common/JobPlatform.kt:12 — enum class JobPlatform(val sourceType: SourceType). 각 값이 단일 sourceType을 고정 보유: JOBKOREA(SourceType.AGGREGATOR) (JobPlatform.kt:20).
domain/company/JobSource.kt:24 — JobSource는 이미 private val sourceType: SourceType를 행 단위로 보유. 즉 도메인 인스턴스 수준에서는 소스마다 유형이 다를 수 있으나, 팩토리가 platform의 고정 유형으로 강제.
infrastructure/company/persistence/JobSourceJpaEntity.kt:23 — source_type 컬럼이 이미 존재(var sourceType: String).
db/migration/V202607220000...sql:53 — source_type VARCHAR(20) NOT NULL 행 단위 컬럼. 유니크 제약 uk_job_sources_company_bound (platform, source_slug) / uk_job_sources_aggregator (platform, search_category_code, search_keyword) — platform+행 속성 조합이라 같은 platform이 두 유형으로 공존해도 충돌하지 않음.
핵심 문제: 행 단위 sourceType은 이미 존재하나, JobPlatform enum의 1:1 고정 + 팩토리 require가 겸용을 봉쇄. 강직성은 도메인 enum 한 곳에 집중돼 있고, 스키마·영속 계층은 이미 겸용 준비 완료.
어댑터 계약 측(BE-02, 미구현 — 코드에 posting 도메인·SPI·어댑터 없음):
TDD 본문(20260722-...-tdd.md:394) SourcePlatformAdapter.supports(platform: JobPlatform): Boolean, capabilitiesOf(platform) (:377), 디스패치 adapters.first { it.supports(platform) } (:403) — 전부 platform 단독 키. 겸용 시 한 플랫폼에 어댑터가 2개(JRS용·www용)라 platform 단독으로는 어느 어댑터·어느 능력인지 결정 불가.
TO-BE
JobPlatform이 supportedSourceTypes: Set<SourceType>를 보유. JOBKOREA = setOf(AGGREGATOR, COMPANY_BOUND), 나머지는 단일 원소 집합.
팩토리는 platform.supports(요청유형)으로 검증 — 잘못된 조합(SARAMIN을 회사 종속형으로) 거부는 유지, 겸용(JOBKOREA)만 통과.
한 플랫폼에 어댑터 2개일 때 (platform, sourceType) 복합 키로 선택 — when 분기 없이 supports() 위임 유지(OCP)
프레임워크 qualifier 대신 도메인 값(SourceType)으로 라우팅 — 인프라 애노테이션을 도메인에 노출하지 않음
Possible Solutions
방안 비교
방안
설명
왜 채택 / 미채택
A. JobPlatform을 지원 집합으로 (채택)
sourceType: SourceType → supportedSourceTypes: Set<SourceType>. JOBKOREA만 2원소, 나머지 1원소. 팩토리 require(platform.supports(X)). 행 단위 source_type은 그대로 사용(이미 존재)
채택. AS-IS에서 행 단위 source_type이 이미 존재해 이 방안이 가장 가벼움(원 티켓이 “폭 큼”으로 본 것은 오해 — 스키마·영속은 이미 겸용 준비 완료). enum 한 곳 + 팩토리 가드만 바꾸면 되고, 잘못된 조합 거부 불변식(SARAMIN을 회사 종속형으로 금지)을 유지. 스키마 마이그레이션 0건
B. JOBKOREA_COMPANY 별도 enum 값 + 별도 어댑터
겸용을 표현하려 플랫폼 값을 쪼갬
미채택. 한 실제 플랫폼에 enum 값 2개 = 오염. categoriesOf(platform)·base_url·중복 판정(대표 선정 sourceType tie-break, TDD:755)이 “잡코리아”를 두 값으로 흩뜨려 처리해야 함. 어댑터를 2개 두는 것(호스트·파서가 실제로 다름)은 정당하나 그건 (platform, sourceType) 디스패치로 해결되지 enum 분리가 필요치 않음
C. 요구사항 축소 (우리은행은 인크루트만)
잡코리아 회사 종속형을 비목표 선언
미채택. robots 실측(아래) 결과 jrs.jobkorea.co.kr가 전면 허용이라 규약상 무산 사유가 없음. 원 요구사항(“인크루트 + 잡코리아”)을 근거 없이 축소할 이유 없음. (robots가 차단이었다면 이 방안이 채택될 예정이었음 — 게이트 통과로 폐기)
AI 크롤러(ClaudeBot·anthropic-ai·GPTBot) 대부분 경로 Disallow: /, 검색 경로 일반 크롤러도 차단
애그리게이터 전용 제약 — 회사 종속형과 다른 호스트라 무관
결론: 배민(BE-08) 폐기는 비공식 내부 API + robots가 사유였으나, 잡코리아 회사 종속형은 별도 호스트(jrs.)가 전면 허용이라 무산 사유 없음. 방안 A 설계 진행. 단, JRS 채용관은 회사별 커스텀 마케팅 셸이라 안정 공고 ID 추출 방식이 미검증(아래 Open Questions) — 이는 모델링(본 티켓)이 아니라 어댑터 티켓의 첫 구현 단계 검증 대상.
Detail Design
시스템 역할 경계
단위
역할
소유 데이터/책임
노출 인터페이스
의존
JobPlatform (enum, domain.common)
플랫폼별 지원 소스 유형 선언
supportedSourceTypes
supports(sourceType): Boolean
SourceType
SourceType (enum, domain.common)
소스 유형 2종
—
값
없음
JobSource (Entity, domain.company)
소스 인스턴스 + 유형 검증
행 단위 sourceType·companyId·sourceSlug·검색조건
forCompany·forAggregator·reconstitute
JobPlatform
JobSourceJpaEntity (infra)
job_sources 매핑
source_type 컬럼(변경 없음)
—
—
SourcePlatformAdapter (SPI, infra.posting — BE-02 미구현, 계약 amend)
서버 토폴로지: 신규 서버 없음. 모델링 변경은 기존 API 서버 도메인 계층 내. 수집은 기존 스케줄러(BE-10 0 0 0 * * *)에 회사 종속형 소스로 자연 편입 — 워커·별도 서버 불필요(1일 1회, 소스 수십 건 규모). 근거: 처리량이 초당 요청이 아닌 일 1회 배치라 단일 서버로 충분.
인터페이스 시그니처
// domain/common/JobPlatform.kt (변경)enum class JobPlatform(val supportedSourceTypes: Set<SourceType>) { GREENHOUSE(setOf(SourceType.COMPANY_BOUND)), WOOWAHAN(setOf(SourceType.COMPANY_BOUND)), INCRUIT(setOf(SourceType.COMPANY_BOUND)), SARAMIN(setOf(SourceType.AGGREGATOR)), JUMPIT(setOf(SourceType.AGGREGATOR)), WANTED(setOf(SourceType.AGGREGATOR)), REMEMBER(setOf(SourceType.AGGREGATOR)), JOBKOREA(setOf(SourceType.AGGREGATOR, SourceType.COMPANY_BOUND)), // 겸용 SURFIT(setOf(SourceType.AGGREGATOR)), ; fun supports(sourceType: SourceType): Boolean = sourceType in supportedSourceTypes}// domain/company/JobSource.kt (변경 — require만)require(platform.supports(SourceType.COMPANY_BOUND)) { "${platform}은 회사 종속형을 지원하지 않습니다" } // forCompanyrequire(platform.supports(SourceType.AGGREGATOR)) { "${platform}은 애그리게이터형을 지원하지 않습니다" } // forAggregator// infra/posting SPI — BE-02 계약 amend (platform 단독 → (platform, sourceType))interface SourcePlatformAdapter { fun supports(platform: JobPlatform, sourceType: SourceType): Boolean val capabilities: SourceCapabilities // 어댑터 인스턴스가 자기 능력 보유 fun fetchList(descriptor: CollectionDescriptor): SourceCollectionOutcome fun supportedCategories(): List<AggregatorCategory>}// JobSourceGatewayImpl: adapters.first { it.supports(descriptor.platform, descriptor.sourceType) }// capabilitiesOf(platform, sourceType)로 변경 — 겸용 플랫폼의 두 능력을 구분
클래스 역할 정의
도메인 모델
클래스명
역할
핵심 책임
JobPlatform
플랫폼 유형 능력 선언
supports(sourceType)로 겸용/전용 판정
JobSource
소스 인스턴스
팩토리에서 platform.supports()로 조합 검증, 행 단위 sourceType 확정
sequenceDiagram
participant U as 등록 UseCase
participant DS as CompanyDomainService
participant S as JobSource
participant P as JobPlatform
U->>DS: 우리은행 잡코리아 소스 등록
DS->>S: forCompany(companyId, JOBKOREA, "wooribank", jrsUrl)
S->>P: supports(COMPANY_BOUND)
P-->>S: true (겸용)
S-->>DS: JobSource(sourceType=COMPANY_BOUND)
DS->>DS: repository.save (uk 멱등)
ERD
스키마 변경 없음. job_sources는 현행 유지.
erDiagram
COMPANIES ||--o{ JOB_SOURCES : has
JOB_SOURCES {
bigint id PK
bigint company_id "NULL이면 애그리게이터"
varchar source_type "행 단위 — 변경 없음"
varchar platform "JOBKOREA 겸용"
varchar source_slug "회사종속: wooribank"
}
핵심 실패 경로: 잘못된 조합 400 유지(회귀), JRS 어댑터 부재 시 0건 회차가 소스 가드로 무해함.
Release Scenario — 무중단 배포
expand-contract·데이터 마이그레이션 불필요 — 스키마 변경 0건, 기존 데이터 무영향. 변경은 팩토리 검증 완화(이전에 거부되던 조합만 신규 허용)라 기존 흐름 동작이 바뀌지 않음.
단계
내용
롤백
1. 모델링 배포
JobPlatform 지원 집합 + JobSource 가드 완화 배포. 기존 소스·API 무영향
코드 revert (하위 호환이라 데이터 정리 불필요)
2. 소스 등록
우리은행 잡코리아 회사 종속형 소스 1건 등록(disabled 아님). 어댑터 미구현이면 수집은 0건이나 소스 가드로 무해
disable() 소프트 오프
3. 어댑터 배포(후속 티켓)
JobkoreaCompanyBoundAdapter 배포 후 수집 시작. 시딩 회차는 알림 없이 저장(BE-10 FR-10)
disable() — robots 정책 변경·파서 고장 시 즉시 중단
피처 플래그 불필요: 검증 완화는 “새 조합 허용”일 뿐 런타임 분기가 아님. 수집 on/off는 기존 disabled_at + posting.auto-close 플래그가 담당.
배포 순서: 코드 먼저(모델링) → 데이터(소스 등록) → 어댑터. 각 단계 독립 배포.
Open Questions
JRS 안정 공고 ID (어댑터 티켓 검증 대상) — jrs.jobkorea.co.kr/{slug}는 회사별 커스텀 마케팅 셸로, 실측(2026-07-27)상 목록에 data-gno·상세 링크가 노출되지 않음. 채용관이 표준 잡코리아 상세(/Recruit/GI_Read/{gno})로 링크아웃하는지, 내부 JSON 엔드포인트가 있는지 어댑터 구현 첫 단계에서 2~3개 실제 채용관으로 확정해야 함. 모델링(본 티켓)에는 영향 없음.
우리은행 잡코리아 실사용 필요성 — 사용자 확인 필요: 회사 종속형 잡코리아 소스를 지금 등록·수집까지 갈지, 모델링만 열어두고 어댑터는 보류할지.
BE-02 구현 상태 — 현재 코드에 posting 도메인·SPI·어댑터 미구현. SPI (platform, sourceType) 디스패치는 BE-02 구현 시점에 반영(계약 amend). BE-02가 이미 착수됐다면 supports(platform) → supports(platform, sourceType) 시그니처 수정 필요.
Document History
날짜
변경 내용
2026-07-27
최초 작성 — robots 게이트 통과(방안 A 채택), 스키마 무변경 확인, SPI (platform, sourceType) 디스패치 amend