프로그래머스 데이터셋 + Gateway 외부 연동

날짜
영향도높음

주요 결정

사전 JSON 번들링 > 실시간 파싱/비공식 API

공식 API 부재 + Cloudflare JA3 차단(Sprint 83 전례) + 문제 풀 유한(373건)/갱신 빈도 낮음. 운영 안정성 우선

검색 API도 번들링 기반

Solvedac과 대칭되는 search 엔드포인트를 인-메모리로 제공 → Sprint 96 프런트에서 동일 UX로 구현 가능

Lv.1~5 ↔ BRONZE~DIAMOND 1:1 매핑

디자인 토큰 0줄 수정으로 기존 Difficulty enum·스타일 토큰 재사용

DTO `@IsIn` 강화

자유 문자열 허용에서 허용값 제한으로 전환. 입력 경계에서 불일치를 조기 차단. DB는 VARCHAR 유지 → Expand-Contract 불필요

브랜치 규율 복원

architect가 실수로 main에 직접 커밋했던 adad5cf를 feat/gateway-programmers-dataset 브랜치로 이동 후 main 리셋(로컬 단계). "main 직접 push 금지" 규칙 준수

배경

백준(BOJ) 서비스 종료로 AlgoSu 문제 제공처를 프로그래머스로 전환해야 한다. 전환 범위가 백엔드·프런트·제출 파이프라인에 걸쳐 광범위해 3개 스프린트 로드맵(Sprint 95 백엔드 인프라 → Sprint 96 프런트 UX → Sprint 97 제출·문서)으로 분할. 본 스프린트는 1단계로, 사용자 가시 변화 0을 원칙으로 백엔드 인프라만 선행 구축한다.

목표

  • 프로그래머스 문제 메타데이터를 사전 큐레이션된 JSON으로 번들링해 검색 UX의 외부 API 의존성 제거
  • Gateway에 BOJ(Solved.ac)와 대칭 구조의 외부 엔드포인트 /api/external/programmers/* 구축
  • DTO sourcePlatform@IsIn(['BOJ','PROGRAMMERS'])로 강화해 플랫폼 허용값 명시
  • 기존 BOJ 경로 회귀 0건 보장

작업 요약

커밋담당내용
adad5cfarchitectProgrammersService/Controller 골격 + external.module.ts 등록 + DTO @IsIn 확장
60b7925librarian데이터 번들링 결정 ADR (docs/adr/topics/sprint-95-programmers-dataset.md)
e460b79postmanPlaywright 기반 크롤러 스크립트 + 초기 데이터셋 373건
18b3932curator데이터 품질 QA 리포트 (PROGRAMMERS-QA.md)
2578ae0gatekeeper검증 리포트 + 749 tests PASS + BOJ 회귀 무결성
aff4b47OracleDTO 타입 강화 후속: problem.service.spec.ts 동기화

수정 내용

Gateway 외부 모듈

  • services/gateway/src/external/programmers.service.ts — JSON 기동 시 로드, Map<problemId, Info> 메모리 캐시, fetchProblem/searchProblem — SolvedacService 인터페이스 대칭
  • services/gateway/src/external/programmers.controller.tsGET /problem/:problemId, GET /search?query=&page=, Swagger External — Programmers
  • services/gateway/src/external/external.module.ts — ProgrammersService/Controller 등록(기존 Solvedac 병치)
  • 데이터 봉투 구조: { version: ISO8601, items: ProgrammersProblemInfo[] } + 레거시 배열 하위 호환(isDataEnvelope() 타입 가드)

크롤러 & 데이터셋

  • services/gateway/scripts/fetch-programmers-problems.ts — Playwright chromium headless로 /learn/challenges?levels=N&order=acceptance_desc&page=M 순회. 레벨별 독립 페이지네이션, 신규 항목 0개 시 종료, 300~500ms 랜덤 딜레이, HTML/URL 로깅 금지
  • services/gateway/data/programmers-problems.json373건 수집 (Lv.1:95 / Lv.2:132 / Lv.3:95 / Lv.4:31 / Lv.5:20), 42840(모의고사) 포함, zod 런타임 검증 통과

DTO

  • services/problem/src/problem/dto/create-problem.dto.tsSOURCE_PLATFORMS = ['BOJ','PROGRAMMERS'] as const 추출, @IsIn(SOURCE_PLATFORMS) 적용, 타입 export
  • DB 마이그레이션 불필요 (기존 source_platform VARCHAR(50) 유지)

테스트 동기화

  • services/problem/src/problem/problem.service.spec.ts L156/L414/L422 — 가상 플랫폼 리터럴('LeetCode','Codeforces') → 'PROGRAMMERS' 교체. sourceUrl은 @IsUrl 검증만 걸려있어 최소 변경 원칙으로 유지

검증 결과

항목결과
Gateway 유닛 테스트 (50 suites / 749 tests)✅ PASS
Gateway tsc --noEmit✅ 오류 0
Gateway ESLint (src + scripts)✅ 오류 0
Problem problem.service.spec.ts (35 tests)✅ PASS (초기 FAIL → Oracle 수정)
BOJ 회귀 (solvedac.{service,controller}.ts diff)✅ 0줄 변경
데이터 품질(중복/누락/인코딩/대표문제 포함)✅ 6 PASS / 1 WARN(tags)

결정

  • 사전 JSON 번들링 > 실시간 파싱/비공식 API: 공식 API 부재 + Cloudflare JA3 차단(Sprint 83 전례) + 문제 풀 유한(373건)/갱신 빈도 낮음. 운영 안정성 우선
  • 검색 API도 번들링 기반: Solvedac과 대칭되는 search 엔드포인트를 인-메모리로 제공 → Sprint 96 프런트에서 동일 UX로 구현 가능
  • Lv.15 ↔ BRONZEDIAMOND 1:1 매핑: 디자인 토큰 0줄 수정으로 기존 Difficulty enum·스타일 토큰 재사용
  • DTO @IsIn 강화: 자유 문자열 허용에서 허용값 제한으로 전환. 입력 경계에서 불일치를 조기 차단. DB는 VARCHAR 유지 → Expand-Contract 불필요
  • 브랜치 규율 복원: architect가 실수로 main에 직접 커밋했던 adad5cf를 feat/gateway-programmers-dataset 브랜치로 이동 후 main 리셋(로컬 단계). "main 직접 push 금지" 규칙 준수