[BE-70] 소스 레지스트리 상태 6종 · 전이 기록 (FR-88)
작업 내용 (설계 의도)
근거 TDD: 20260808-지원관리-확장-tdd.md — “방안 5 (소스 상태 → company 합류)”, “상태 전이 표 소스 레지스트리 상태”
변경 사항
현재 소스 상태는 2축 + 별도 테이블입니다 — job_sources.seeded_at·disabled_at(baseline...sql:59-60)와 job_source_health(:152-164). 6종 상태와 “마지막 전이 시각”(PRD Operations 요구)을 담을 자리가 없습니다.
company컨텍스트에 합류합니다 —job_sources가 company 소유이므로 상태는 그 테이블의 컬럼 확장입니다. 신규 컨텍스트를 만들지 않습니다.- 파생 계산이 아니라 영속 상태로 둡니다 —
registry_status·registry_status_changed_at. 파생 계산으로는 “마지막 전이 시각”을 만들 수 없습니다. - 전이 규칙을
JobSourceRegistryStatus.canTransitTo()에 캡슐화합니다 — 등록 →DISCOVERED/ 어댑터 없음 →UNSUPPORTED/ 수집 성공 →ACTIVE/ 연속 실패 3일 미만 →TRANSIENT_FAILURE/ 401·403·robots 차단 →ACCESS_RESTRICTED/ 사용자 비활성 →DISABLED/DISABLED해제 →DISCOVERED(바로ACTIVE로 보내지 않고 재검증부터). - 별도
BROKEN상태를 만들지 않습니다 — PRD가 6종을 확정했고, 3일 연속 고장 여부는job_source_health.broken_since가 이미 표현합니다.TRANSIENT_FAILURE를 유지하며 기존 소스 고장 알림(FR-19)은 그대로 동작합니다. - 전이 트리거는 posting(수집 결과)이 알고 전이 실행은 company가 합니다 — 크로스 컨텍스트라
ApplyJobSourceRegistryOutcomeUseCase(application 레이어)가 조합합니다. posting DomainService가 회차 결과를 도메인 값 객체(SourceCollectionOutcome)로 넘기고, application 매퍼가 company 입력으로 변환합니다. - 초기값 산출(데이터 마이그레이션 2단계) —
recalculateAll()이seeded_at·disabled_at·job_source_health·어댑터 보유 여부로 6종 초기값을 계산합니다. 대상 30행 미만(NFR-1)이라 페이지 순회·청크가 불필요합니다.registry_status IS NULL인 행만 대상이라 재실행 멱등입니다. 이 근거를 코드 주석에 남깁니다. - 피처 플래그
source.registry-status가 OFF면 전이를 기록하지 않습니다(컬럼은 유지).
롤백: 플래그 OFF. 컬럼을 다시 NULL로 되돌리는 UPDATE는 30행이라 즉시 가능합니다.
의존
- BE-68 (플래그 시드)
- DB-04 (
job_sources컬럼 2개)
다이어그램
처리 흐름
sequenceDiagram participant U as ApplyJobSourceRegistryOutcomeUseCase participant P as PostingDomainService participant M as SourceOutcomeMapper participant C as JobSourceRegistryDomainService participant S as JobSource U->>P: 수집 회차 결과 조회 P-->>U: SourceCollectionOutcome (posting 값 객체) U->>M: company 입력 값 객체로 변환 U->>C: applyCollectionOutcome(jobSourceId, outcome) C->>S: 현재 상태 × 트리거 → 다음 상태 판정 alt 전이 없음 S-->>C: false else 전이 S->>S: registryStatus 변경 + changedAt 기록 C-->>U: true end
클래스 의존
flowchart LR subgraph Application["application/company"] UC[ApplyJobSourceRegistryOutcomeUseCase] Mapper[SourceOutcomeMapper] Backfill[BackfillSourceRegistryUseCase] end subgraph Domain["domain/company"] DS[JobSourceRegistryDomainService] Status[JobSourceRegistryStatus] Source[JobSource] Repo[JobSourceRepository] Flag[FeatureFlagGateway] end UC --> Mapper UC --> DS Backfill --> DS DS --> Source DS --> Repo DS --> Flag Source --> Status
테스트 케이스
- 등록만 되고 수집되지 않은 소스는
DISCOVERED다 - 수집이 성공(
fetched_count > 0)하면ACTIVE로 전이하고 전이 시각이 기록된다 ACTIVE소스가 수집 실패하면TRANSIENT_FAILURE로 전이한다TRANSIENT_FAILURE소스가 수집 성공하면ACTIVE로 복귀한다- 403 응답이면
ACCESS_RESTRICTED로 전이한다 ACCESS_RESTRICTED소스가 수집 성공하면ACTIVE로 복귀한다- 어댑터가 없는 플랫폼은
UNSUPPORTED다 - 사용자가 소스를 끄면(
disabled_at설정)DISABLED다 DISABLED소스를 다시 켜면ACTIVE가 아니라DISCOVERED로 복귀한다- 같은 상태로의 전이는
false를 반환하고 전이 시각을 갱신하지 않는다 - 3일 연속 실패해도 상태는
TRANSIENT_FAILURE를 유지하고 별도BROKEN상태가 생기지 않는다 - 3일 연속 실패 시 기존 소스 고장 알림(FR-19)이 그대로 발송된다 (회귀)
- 백필을 실행하면
registry_status IS NULL인 소스가 0건이 된다 - 백필을 두 번 실행하면 두 번째는 처리 건수가 0이다 (멱등)
- 피처 플래그 OFF면 전이가 기록되지 않고 컬럼 값이 유지된다