Skip to content

Refactoring Plan

This content is not available in your language yet.

파일현재 라인 수규칙 제한위반 정도우선순위
.github/workflows/deploy.yml802Shell First (인라인 금지)🔴 심각P0
src/services/research.service.ts747200 lines🔴 3.7배 초과P0
src/services/seed.service.ts644200 lines🔴 3.2배 초과P0
src/services/storage.service.ts543150 lines🔴 3.6배 초과P0
src/lib/kv.ts502200 lines🔴 2.5배 초과P1
src/lib/d1.ts441200 lines🔴 2.2배 초과P1
src/services/queue.service.ts416200 lines🔴 2.1배 초과P1
src/lib/queue.ts405200 lines🔴 2.0배 초과P1
src/lib/liveness.ts399200 lines🔴 2.0배 초과P1
src/lib/path.ts381200 lines🔴 1.9배 초과P1
src/schemas/seed.ts381100 lines🔴 3.8배 초과P1
src/index.ts307120 lines🔴 2.6배 초과P1

총 위반 파일: 12개

  • P0 (즉시 수정): 4개
  • P1 (우선 수정): 8개

  • 모든 파일이 규칙 제한 내로 분해
  • 재사용 가능한 모듈 구조
  • 테스트 가능한 순수 함수
  • deploy.yml에서 모든 로직을 Shell 스크립트로 이동
  • CI와 로컬 환경 100% 재현 가능
  • Domain: Cloudflare API 없음 (순수 함수)
  • Infra: Cloudflare API만 포함

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 info

1.3 신규 Shell 스크립트

.github/scripts/steps/provision-d1.sh (신규, ~80 lines)

#!/usr/bin/env bash
# Provision D1 Database
set -euxo pipefail
source "$(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 Namespace
set -euxo pipefail
source "$(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 IDs
set -euxo pipefail
source "$(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 <<EOF
import 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 초과

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

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 어댑터가 하나의 파일에 모두 포함

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)
  • 책임 분리: 각 어댑터는 하나의 인프라만 담당

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)

  1. Week 1: deploy.yml 분해 (Shell First 전략)
  2. Week 2: research.service.ts, seed.service.ts 분해
  1. Week 3: storage.service.ts 분해
  2. Week 4: lib/ 파일들 분해
  3. Week 5: schemas/seed.ts, index.ts 분해
  1. Week 6: 테스트 작성, 통합 테스트, 검증

총 예상 기간: 4-6주


  • 모든 파일이 규칙 제한 내
  • 함수 크기 30-50 lines 이내
  • Domain 모듈 200 lines 이내
  • Infra 어댑터 150 lines 이내
  • Domain과 Infra 분리 완료
  • Shell First 전략 적용 완료
  • 재사용 가능한 모듈 구조
  • 테스트 가능한 순수 함수
  • 기존 기능 동작 확인
  • 테스트 통과
  • CI/CD 파이프라인 정상 작동
  • 로컬 환경에서 CI 재현 가능

  • 파일 크기 감소로 이해하기 쉬움
  • 책임 분리로 수정 범위 명확
  • 재사용 가능한 모듈로 중복 제거
  • Domain 함수는 Mock 불필요
  • 순수 함수로 단위 테스트 용이
  • Infra 어댑터는 독립적으로 테스트 가능
  • 로컬에서 CI 재현 가능
  • Shell 스크립트로 빠른 반복 개발
  • 모듈화로 병렬 개발 가능
  • 새로운 도메인 추가 용이
  • 새로운 인프라 어댑터 추가 용이
  • 마이크로서비스화 가능

  1. 점진적 리팩토링: 한 번에 하나씩, 테스트하면서 진행
  2. 기능 보존: 리팩토링 중 기능 변경 금지
  3. 테스트 우선: 리팩토링 전 테스트 작성
  4. 작은 커밋: 각 모듈 분해를 별도 커밋
  • 기존 API 인터페이스 유지 (호환성)
  • 점진적 마이그레이션 (Big Bang 금지)
  • 충분한 테스트 커버리지 확보