Refactoring Plan
📊 현재 상태 분석
섹션 제목: “📊 현재 상태 분석”파일 크기 위반 현황
섹션 제목: “파일 크기 위반 현황”| 파일 | 현재 라인 수 | 규칙 제한 | 위반 정도 | 우선순위 |
|---|---|---|---|---|
.github/workflows/deploy.yml | 802 | Shell First (인라인 금지) | 🔴 심각 | P0 |
src/services/research.service.ts | 747 | 200 lines | 🔴 3.7배 초과 | P0 |
src/services/seed.service.ts | 644 | 200 lines | 🔴 3.2배 초과 | P0 |
src/services/storage.service.ts | 543 | 150 lines | 🔴 3.6배 초과 | P0 |
src/lib/kv.ts | 502 | 200 lines | 🔴 2.5배 초과 | P1 |
src/lib/d1.ts | 441 | 200 lines | 🔴 2.2배 초과 | P1 |
src/services/queue.service.ts | 416 | 200 lines | 🔴 2.1배 초과 | P1 |
src/lib/queue.ts | 405 | 200 lines | 🔴 2.0배 초과 | P1 |
src/lib/liveness.ts | 399 | 200 lines | 🔴 2.0배 초과 | P1 |
src/lib/path.ts | 381 | 200 lines | 🔴 1.9배 초과 | P1 |
src/schemas/seed.ts | 381 | 100 lines | 🔴 3.8배 초과 | P1 |
src/index.ts | 307 | 120 lines | 🔴 2.6배 초과 | P1 |
총 위반 파일: 12개
- P0 (즉시 수정): 4개
- P1 (우선 수정): 8개
🎯 리팩토링 목표
섹션 제목: “🎯 리팩토링 목표”1. .cursorrules 규칙 준수
섹션 제목: “1. .cursorrules 규칙 준수”- 모든 파일이 규칙 제한 내로 분해
- 재사용 가능한 모듈 구조
- 테스트 가능한 순수 함수
2. Shell First 전략 완전 적용
섹션 제목: “2. Shell First 전략 완전 적용”deploy.yml에서 모든 로직을 Shell 스크립트로 이동- CI와 로컬 환경 100% 재현 가능
3. Domain/Infra 분리
섹션 제목: “3. Domain/Infra 분리”- Domain: Cloudflare API 없음 (순수 함수)
- Infra: Cloudflare API만 포함
📋 우선순위별 리팩토링 계획
섹션 제목: “📋 우선순위별 리팩토링 계획”P0: 즉시 수정 (심각한 위반)
섹션 제목: “P0: 즉시 수정 (심각한 위반)”1. .github/workflows/deploy.yml (802 lines → ~150 lines)
섹션 제목: “1. .github/workflows/deploy.yml (802 lines → ~150 lines)”현재 문제점
섹션 제목: “현재 문제점”- D1 Database provisioning 로직이 120+ lines 인라인 스크립트
- KV Namespace provisioning 로직이 60+ lines 인라인 스크립트
- wrangler.jsonc 업데이트 로직이 100+ lines 인라인 스크립트
- Production 배포 job이 거의 동일한 로직 중복 (200+ lines)
개선 방안
섹션 제목: “개선 방안”1.1 Shell 스크립트로 로직 이동
.github/scripts/├── steps/│ ├── provision-d1.sh # D1 Database provisioning (신규)│ ├── provision-kv.sh # KV Namespace provisioning (신규)│ ├── update-wrangler-config.sh # wrangler.jsonc 업데이트 (신규)│ └── deploy.sh # 기존 (유지)└── lib/ ├── cloudflare-resources.sh # Cloudflare 리소스 유틸리티 (신규) └── wrangler-config.sh # wrangler.jsonc 조작 유틸리티 (신규)1.2 deploy.yml 구조 개선
# ✅ 개선 후 구조 (150 lines 예상)jobs: deploy: steps: - name: Checkout uses: actions/checkout@v4
- name: Setup Environment run: ./.github/scripts/setup.sh
- name: Provision Cloudflare Resources run: ./.github/scripts/steps/provision-resources.sh env: ENV: ${{ steps.env.outputs.environment }}
- name: Deploy to Cloudflare Workers run: ./.github/scripts/steps/deploy.sh env: DEPLOY_ENV: ${{ steps.env.outputs.environment }} # ... build info1.3 신규 Shell 스크립트
.github/scripts/steps/provision-d1.sh (신규, ~80 lines)
#!/usr/bin/env bash# Provision D1 Databaseset -euxo pipefailsource "$(dirname "$0")/../lib/debug.sh"source "$(dirname "$0")/../lib/cloudflare-resources.sh"
ENV="${DEPLOY_ENV:-staging}"DB_NAME="newsfork-metadata-${ENV}"
# 1. Check wrangler.jsonc for existing ID# 2. List existing databases# 3. Create if not exists# 4. Extract and validate ID# 5. Output to GITHUB_OUTPUT.github/scripts/steps/provision-kv.sh (신규, ~60 lines)
#!/usr/bin/env bash# Provision KV Namespaceset -euxo pipefailsource "$(dirname "$0")/../lib/debug.sh"source "$(dirname "$0")/../lib/cloudflare-resources.sh"
ENV="${DEPLOY_ENV:-staging}"NAMESPACE="DOMAIN_KV"
# 1. List existing namespaces# 2. Create if not exists# 3. Extract and validate ID# 4. Output to GITHUB_OUTPUT.github/scripts/steps/update-wrangler-config.sh (신규, ~50 lines)
#!/usr/bin/env bash# Update wrangler.jsonc with resource IDsset -euxo pipefailsource "$(dirname "$0")/../lib/debug.sh"source "$(dirname "$0")/../lib/wrangler-config.sh"
ENV="${DEPLOY_ENV:-staging}"D1_ID="${D1_DATABASE_ID:-}"KV_ID="${KV_NAMESPACE_ID:-}"
# Python script로 wrangler.jsonc 업데이트python3 <<EOFimport jsonc# wrangler.jsonc 업데이트 로직EOF.github/scripts/lib/cloudflare-resources.sh (신규, ~100 lines)
#!/usr/bin/env bash# Cloudflare 리소스 관리 유틸리티set -euxo pipefail
# D1 Database 관련 함수get_d1_database_id() { ... }create_d1_database() { ... }list_d1_databases() { ... }
# KV Namespace 관련 함수get_kv_namespace_id() { ... }create_kv_namespace() { ... }list_kv_namespaces() { ... }.github/scripts/lib/wrangler-config.sh (신규, ~80 lines)
#!/usr/bin/env bash# wrangler.jsonc 조작 유틸리티set -euxo pipefail
# Python을 사용한 JSONC 조작update_wrangler_d1_id() { ... }update_wrangler_kv_id() { ... }validate_wrangler_config() { ... }예상 결과:
deploy.yml: 802 lines → ~150 lines (81% 감소)- Shell 스크립트로 로직 이동: 로컬에서도 실행 가능
- Production job 중복 제거: 공통 스크립트 재사용
2. src/services/research.service.ts (747 lines → ~200 lines × 4 files)
섹션 제목: “2. src/services/research.service.ts (747 lines → ~200 lines × 4 files)”현재 문제점
섹션 제목: “현재 문제점”- 단일 파일에 모든 Research 로직 포함
- Domain과 Infra 코드 혼재
- 함수들이 50+ lines 초과
개선 방안: Domain 모듈로 분해
섹션 제목: “개선 방안: Domain 모듈로 분해”2.1 목표 구조
src/├── domain/│ └── research/│ ├── discoverUrlsFromSource.ts # URL 발견 (신규, ~150 lines)│ ├── validateDatasetIntegrity.ts # 데이터 검증 (신규, ~100 lines)│ ├── normalizeDomain.ts # 도메인 정규화 (신규, ~80 lines)│ ├── calculateLivenessStatus.ts # Liveness 계산 (신규, ~120 lines)│ └── index.ts # Export (신규, ~20 lines)│├── infra/│ └── cloudflare/│ ├── r2/│ │ ├── storeResearchDataset.ts # R2 저장 (신규, ~100 lines)│ │ ├── retrieveResearchDataset.ts # R2 조회 (신규, ~80 lines)│ │ └── listResearchDatasets.ts # R2 목록 (신규, ~60 lines)│ └── d1/│ └── saveResearchMetadata.ts # D1 메타데이터 저장 (신규, ~80 lines)│└── services/ └── research.service.ts # 얇은 서비스 레이어 (신규, ~150 lines)2.2 Domain 함수 분해
src/domain/research/discoverUrlsFromSource.ts (신규, ~150 lines)
// ✅ 순수 함수 (Cloudflare API 없음)export function discoverUrlsFromSource( input: DiscoverUrlsInput): DiscoverUrlsOutput { // URL 발견 로직 // Domain 정규화 // 중복 제거 // 반환}src/domain/research/validateDatasetIntegrity.ts (신규, ~100 lines)
// ✅ 순수 함수export function validateDatasetIntegrity( dataset: ResearchDataset): ValidationResult { // 메타데이터 검증 // URL 목록 검증 // 스키마 검증}src/domain/research/calculateLivenessStatus.ts (신규, ~120 lines)
// ✅ 순수 함수export function calculateLivenessStatus( results: LivenessCheckResult[]): LivenessStatus { // Liveness 상태 계산 // 통계 집계 // 상태 결정}2.3 Infra 어댑터 분해
src/infra/cloudflare/r2/storeResearchDataset.ts (신규, ~100 lines)
// ✅ R2 전용 코드만export async function storeResearchDataset( bucket: R2Bucket, path: string, dataset: ResearchDataset): Promise<void> { // R2 저장 로직}2.4 얇은 서비스 레이어
src/services/research.service.ts (개선 후, ~150 lines)
// ✅ 얇은 서비스 레이어 (Domain + Infra 조합)export class ResearchService { constructor( private readonly storage: IStorageService, private readonly r2Bucket: R2Bucket, private readonly d1Db: D1Database ) {}
async get(...) { // Domain 함수 호출 const dataset = await retrieveResearchDataset(this.r2Bucket, path); return dataset; }
async create(...) { // Domain 함수로 데이터 생성 const dataset = discoverUrlsFromSource(input); // Infra로 저장 await storeResearchDataset(this.r2Bucket, path, dataset); }}예상 결과:
research.service.ts: 747 lines → ~150 lines (80% 감소)- Domain 모듈: 4개 파일로 분해 (각 80-150 lines)
- Infra 어댑터: 3개 파일로 분해 (각 60-100 lines)
- 테스트 가능성 향상: Domain 함수는 Mock 불필요
3. src/services/seed.service.ts (644 lines → ~200 lines × 3 files)
섹션 제목: “3. src/services/seed.service.ts (644 lines → ~200 lines × 3 files)”현재 문제점
섹션 제목: “현재 문제점”- 단일 파일에 모든 Seed 로직 포함
- Domain과 Infra 코드 혼재
generateEnhancedSeed함수가 100+ lines
개선 방안: Domain 모듈로 분해
섹션 제목: “개선 방안: Domain 모듈로 분해”3.1 목표 구조
src/├── domain/│ └── seed/│ ├── createSeedContract.ts # Seed 생성 (신규, ~150 lines)│ ├── validateSeedContract.ts # Seed 검증 (신규, ~100 lines)│ ├── promoteSeedToActive.ts # Seed 승격 (신규, ~120 lines)│ └── index.ts # Export (신규, ~20 lines)│├── infra/│ └── cloudflare/│ └── github/│ ├── storeSeedContract.ts # GitHub 저장 (신규, ~80 lines)│ └── retrieveSeedContract.ts # GitHub 조회 (신규, ~80 lines)│└── services/ └── seed.service.ts # 얇은 서비스 레이어 (신규, ~150 lines)3.2 Domain 함수 분해
src/domain/seed/createSeedContract.ts (신규, ~150 lines)
// ✅ 순수 함수export function createSeedContract( input: SeedCreateRequest): SeedContract { // Seed 계약 생성 // 도메인 정규화 // 기본값 설정 // 검증}src/domain/seed/validateSeedContract.ts (신규, ~100 lines)
// ✅ 순수 함수export function validateSeedContract( seed: SeedContract): ValidationResult { // 스키마 검증 // 비즈니스 규칙 검증 // 중복 검사}src/domain/seed/promoteSeedToActive.ts (신규, ~120 lines)
// ✅ 순수 함수export function promoteSeedToActive( seed: SeedContract): SeedContract { // 상태 변경 // 메타데이터 업데이트 // 검증}3.3 얇은 서비스 레이어
src/services/seed.service.ts (개선 후, ~150 lines)
// ✅ 얇은 서비스 레이어export class SeedService { constructor(private readonly storage: IStorageService) {}
async create(input: SeedCreateRequest) { const seed = createSeedContract(input); await storeSeedContract(this.storage, seed); return seed; }
async promote(seedId: string) { const seed = await retrieveSeedContract(this.storage, seedId); const promoted = promoteSeedToActive(seed); await storeSeedContract(this.storage, promoted); return promoted; }}예상 결과:
seed.service.ts: 644 lines → ~150 lines (77% 감소)- Domain 모듈: 3개 파일로 분해 (각 100-150 lines)
- 테스트 가능성 향상
4. src/services/storage.service.ts (543 lines → ~150 lines × 4 files)
섹션 제목: “4. src/services/storage.service.ts (543 lines → ~150 lines × 4 files)”현재 문제점
섹션 제목: “현재 문제점”- 단일 파일에 GitHub API, R2, D1 로직 혼재
- Infra 어댑터가 하나의 파일에 모두 포함
개선 방안: Infra 어댑터로 분해
섹션 제목: “개선 방안: Infra 어댑터로 분해”4.1 목표 구조
src/├── infra/│ └── cloudflare/│ ├── github/│ │ ├── githubStorageAdapter.ts # GitHub Storage (신규, ~150 lines)│ │ ├── readFromGitHub.ts # GitHub 읽기 (신규, ~80 lines)│ │ ├── writeToGitHub.ts # GitHub 쓰기 (신규, ~100 lines)│ │ └── listFromGitHub.ts # GitHub 목록 (신규, ~60 lines)│ ├── r2/│ │ └── r2StorageAdapter.ts # R2 Storage (신규, ~150 lines)│ └── hybrid/│ └── hybridStorageAdapter.ts # Hybrid Storage (신규, ~150 lines)│└── services/ └── storage.service.ts # 인터페이스만 (신규, ~50 lines)4.2 Infra 어댑터 분해
src/infra/cloudflare/github/githubStorageAdapter.ts (신규, ~150 lines)
// ✅ GitHub API 전용 코드만export class GitHubStorageAdapter implements IStorageService { constructor( private readonly token: string, private readonly owner: string, private readonly repo: string ) {}
async read(path: string): Promise<string | null> { // GitHub API 호출 }
async write(path: string, content: string, commit?: CommitInfo): Promise<void> { // GitHub API 호출 }}src/infra/cloudflare/r2/r2StorageAdapter.ts (신규, ~150 lines)
// ✅ R2 API 전용 코드만export class R2StorageAdapter implements IStorageService { constructor( private readonly bucket: R2Bucket, private readonly prefix?: string ) {}
async read(path: string): Promise<string | null> { // R2 API 호출 }
async write(path: string, content: string): Promise<void> { // R2 API 호출 }}예상 결과:
storage.service.ts: 543 lines → ~50 lines (인터페이스만)- Infra 어댑터: 3개 파일로 분해 (각 150 lines)
- 책임 분리: 각 어댑터는 하나의 인프라만 담당
P1: 우선 수정 (중요한 위반)
섹션 제목: “P1: 우선 수정 (중요한 위반)”5. src/lib/kv.ts (502 lines → ~200 lines × 3 files)
섹션 제목: “5. src/lib/kv.ts (502 lines → ~200 lines × 3 files)”분해 계획:
src/lib/├── kv/│ ├── deduplication.ts # 중복 제거 로직 (신규, ~150 lines)│ ├── domain-cache.ts # 도메인 캐시 (신규, ~120 lines)│ └── kv-config.ts # KV 설정 (신규, ~80 lines)6. src/lib/d1.ts (441 lines → ~200 lines × 3 files)
섹션 제목: “6. src/lib/d1.ts (441 lines → ~200 lines × 3 files)”분해 계획:
src/lib/├── d1/│ ├── metadata-queries.ts # 메타데이터 쿼리 (신규, ~150 lines)│ ├── task-state.ts # 작업 상태 (신규, ~120 lines)│ └── d1-utils.ts # D1 유틸리티 (신규, ~80 lines)7. src/lib/queue.ts (405 lines → ~200 lines × 2 files)
섹션 제목: “7. src/lib/queue.ts (405 lines → ~200 lines × 2 files)”분해 계획:
src/lib/├── queue/│ ├── queue-handlers.ts # Queue 핸들러 (신규, ~200 lines)│ └── queue-utils.ts # Queue 유틸리티 (신규, ~100 lines)8. src/lib/liveness.ts (399 lines → ~200 lines × 2 files)
섹션 제목: “8. src/lib/liveness.ts (399 lines → ~200 lines × 2 files)”분해 계획:
src/lib/├── liveness/│ ├── liveness-check.ts # Liveness 체크 (신규, ~200 lines)│ └── liveness-calculator.ts # Liveness 계산 (신규, ~120 lines)9. src/lib/path.ts (381 lines → ~200 lines × 2 files)
섹션 제목: “9. src/lib/path.ts (381 lines → ~200 lines × 2 files)”분해 계획:
src/lib/├── path/│ ├── path-builders.ts # 경로 빌더 (신규, ~200 lines)│ └── path-parsers.ts # 경로 파서 (신규, ~120 lines)10. src/schemas/seed.ts (381 lines → ~100 lines × 4 files)
섹션 제목: “10. src/schemas/seed.ts (381 lines → ~100 lines × 4 files)”분해 계획:
src/schemas/├── seed/│ ├── seed-contract.ts # SeedContract 스키마 (신규, ~100 lines)│ ├── seed-source.ts # Source 스키마 (신규, ~80 lines)│ ├── seed-discovery.ts # Discovery 스키마 (신규, ~80 lines)│ └── seed-contents.ts # Contents 스키마 (신규, ~80 lines)11. src/index.ts (307 lines → ~120 lines)
섹션 제목: “11. src/index.ts (307 lines → ~120 lines)”분해 계획:
src/├── apps/│ └── api/│ ├── index.ts # 얇은 진입점 (신규, ~80 lines)│ ├── http-handler.ts # HTTP 핸들러 (신규, ~100 lines)│ ├── scheduled-handler.ts # Cron 핸들러 (신규, ~80 lines)│ └── queue-handler.ts # Queue 핸들러 (신규, ~100 lines)📅 리팩토링 일정 (예상)
섹션 제목: “📅 리팩토링 일정 (예상)”Phase 1: P0 수정 (1-2주)
섹션 제목: “Phase 1: P0 수정 (1-2주)”- Week 1:
deploy.yml분해 (Shell First 전략) - Week 2:
research.service.ts,seed.service.ts분해
Phase 2: P1 수정 (2-3주)
섹션 제목: “Phase 2: P1 수정 (2-3주)”- Week 3:
storage.service.ts분해 - Week 4:
lib/파일들 분해 - Week 5:
schemas/seed.ts,index.ts분해
Phase 3: 테스트 및 검증 (1주)
섹션 제목: “Phase 3: 테스트 및 검증 (1주)”- Week 6: 테스트 작성, 통합 테스트, 검증
총 예상 기간: 4-6주
✅ 리팩토링 완료 체크리스트
섹션 제목: “✅ 리팩토링 완료 체크리스트”파일 크기 검증
섹션 제목: “파일 크기 검증”- 모든 파일이 규칙 제한 내
- 함수 크기 30-50 lines 이내
- Domain 모듈 200 lines 이내
- Infra 어댑터 150 lines 이내
구조 검증
섹션 제목: “구조 검증”- Domain과 Infra 분리 완료
- Shell First 전략 적용 완료
- 재사용 가능한 모듈 구조
- 테스트 가능한 순수 함수
기능 검증
섹션 제목: “기능 검증”- 기존 기능 동작 확인
- 테스트 통과
- CI/CD 파이프라인 정상 작동
- 로컬 환경에서 CI 재현 가능
🎯 기대 효과
섹션 제목: “🎯 기대 효과”1. 유지보수성 향상
섹션 제목: “1. 유지보수성 향상”- 파일 크기 감소로 이해하기 쉬움
- 책임 분리로 수정 범위 명확
- 재사용 가능한 모듈로 중복 제거
2. 테스트 가능성 향상
섹션 제목: “2. 테스트 가능성 향상”- Domain 함수는 Mock 불필요
- 순수 함수로 단위 테스트 용이
- Infra 어댑터는 독립적으로 테스트 가능
3. 개발 생산성 향상
섹션 제목: “3. 개발 생산성 향상”- 로컬에서 CI 재현 가능
- Shell 스크립트로 빠른 반복 개발
- 모듈화로 병렬 개발 가능
4. 확장성 향상
섹션 제목: “4. 확장성 향상”- 새로운 도메인 추가 용이
- 새로운 인프라 어댑터 추가 용이
- 마이크로서비스화 가능
📝 참고사항
섹션 제목: “📝 참고사항”리팩토링 원칙
섹션 제목: “리팩토링 원칙”- 점진적 리팩토링: 한 번에 하나씩, 테스트하면서 진행
- 기능 보존: 리팩토링 중 기능 변경 금지
- 테스트 우선: 리팩토링 전 테스트 작성
- 작은 커밋: 각 모듈 분해를 별도 커밋
주의사항
섹션 제목: “주의사항”- 기존 API 인터페이스 유지 (호환성)
- 점진적 마이그레이션 (Big Bang 금지)
- 충분한 테스트 커버리지 확보