모놀리식 CLI에서 모듈형 플러그인으로: 스트랭글러 피그 패턴 적용
Source: Dev.to
TL;DR – 안전한 마이그레이션 전략
- 명령을 외부 플러그인으로 추출 (베타로 배포)
- 플러그인을 핵심에 의존성으로 추가 (v2.x – 파괴적인 변경 없음)
- 피드백을 수집하고, 반복하며, 안정화
- 의존성 제거를 v3.0.0에서 수행 (파괴적인 변경이지만 준비됨)
- 사용자는 필요한 것만 설치
Result: 안전한 마이그레이션 + 독립적인 플러그인 진화
문제: CLI가 유지보수 불가능해지는 이유
당신의 CLI는 아마 다음과 같이 시작했을 것입니다:
cli/
├── commands/
│ ├── deploy.js
│ └── status.js
├── utils/
│ └── helpers.js
└── index.js
깔끔하고, 단순하며, 모든 것이 제자리를 찾고 있었습니다.
6개월 후:
cli/
├── commands/ (37 files)
├── utils/ (23 files)
├── services/ (15 files)
├── integrations/ (12 files)
└── shared/ (who even knows?)
이제 당신은 다음과 같은 문제에 직면합니다:
- 강한 결합 – 모든 명령이 10개 이상의 유틸리티 파일을 import
- 공유 상태 악몽 – 모든 것이 건드리는 전역 설정
- 위험한 릴리즈 – 하나의 변경으로 전체 CLI를 테스트해야 함
- 느린 개발 – 기능 추가에 몇 주씩 협업이 필요
- 파괴적 변경 지옥 – 한 부분을 진화시키면 전체가 깨짐
익숙한가요? 당신은 모놀리스를 만들었습니다.
백엔드 교훈: 모놀리식에서 모듈러로
옛 방식 (모놀리식 API)
Everything in one codebase
↓
Tightly coupled domains
↓
Coordinated releases
↓
High‑risk deployments
↓
Slow feature velocity
현대 방식 (Microservices / Modular)
Thin API Gateway
↓
Isolated domain services
↓
Independent deployments
↓
Versioned contracts
↓
Fast, safe iterations
핵심 인사이트: 도메인을 격리하고, 명확한 경계를 설정하며, 독립적인 진화를 가능하게 하세요. 이는 CLI에도 적용됩니다.
해결책: Core + 플러그인 아키텍처
얇은 코어
Your core should be boring and stable:
core/
├── auth/ // Auth logic
├── config/ // Config management
├── dispatcher/ // Command routing
├── utils/ // Cross‑cutting concerns
└── plugin-loader/ // Plugin discovery
The core provides infrastructure and gets out of the way.
플러그인
Each plugin is a self‑contained domain:
plugins/
├── deploy-plugin/
│ ├── commands/
│ ├── tests/
│ ├── README.md
│ └── package.json // Independent versioning!
├── logs-plugin/
└── backup-plugin/
각 플러그인:
- 정확히 하나의 책임을 가집니다
- 자체 시맨틱 버전을 가집니다
- 독립적으로 설치됩니다 (
npm i @cli/deploy-plugin) - 다른 플러그인에 영향을 주지 않고 변경됩니다
- 완전한 격리 상태에서 테스트됩니다
마이그레이션 전략: 스트랭글러 피그 패턴
스트랭글러 피그 패턴(마틴 파울러)은 위험한 대대적인 리라이트 없이 점진적으로 마이그레이션할 수 있게 해줍니다.
Step 1 – 외부 플러그인 베타 릴리스
# Release your first plugin in beta
npm publish @my-cli/deploy-plugin@1.0.0-beta.1
npm publish @my-cli/logs-plugin@1.0.0-beta.1
왜 베타인가? 사용자는 테스트를 위해 옵트인할 수 있고, 빠르게 반복할 수 있으며, 파괴적인 변경이 예상됩니다.
// deploy-plugin/package.json
{
"name": "@my-cli/deploy-plugin",
"version": "1.0.0-beta.1",
"main": "dist/index.js",
"peerDependencies": {
"@my-cli/core": "^2.0.0"
}
}
Step 2 – 코어에 플러그인을 임시 종속성으로 추가
// core/package.json
{
"name": "@my-cli/core",
"version": "2.5.0",
"dependencies": {
"@my-cli/deploy-plugin": "^1.0.0-beta.1",
"@my-cli/logs-plugin": "^1.0.0-beta.1"
}
}
- 사용자는
core를 설치하면 → 플러그인이 자동으로 함께 설치됩니다 - 기존 사용자에게는 파괴적인 변화가 없습니다
- 플러그인은 초기 채택자를 위해 독립적으로 동작합니다
Step 3 – 명령어를 점진적으로 마이그레이션
v2.5.0 (Initial Beta)
deploy → Plugin (bundled as dependency)
logs → Legacy in core
backup → Legacy in core
v2.8.0 (Beta 2)
deploy → Plugin (bundled as dependency)
logs → Plugin (bundled as dependency)
backup → Legacy in core
각 단계는 안전하게 배포되며, 사용자는 차이를 느끼지 못하고 플러그인은 실제 환경에서 성숙해집니다.
Step 4 – 대전환: 종속성 제거 (v3.0.0)
// core/package.json (v3.0.0)
{
"name": "@my-cli/core",
"version": "3.0.0",
"dependencies": {
// No more plugin dependencies!
},
"peerDependencies": {
// Plugins are now optional
}
}
사용자를 위한 마이그레이션 가이드
# Old way (v2.x) – everything bundled
npm install -g @my-cli/core
# New way (v3.x) – install what you need
npm install -g @my-cli/core
npm install -g @my-cli/deploy-plugin # only if you need deploy
npm install -g @my-cli/logs-plugin # only if you need logs
왜 이것이 메이저 버전인가: 파괴적인 변화 – 플러그인이 더 이상 자동 설치되지 않아 사용자가 더 많은 제어권을 갖게 되고, 설치 용량이 작아지며, 설치 속도가 빨라집니다.
Step 5 – 필요에 따라 사용자 설치
# Minimal installation (just core utilities)
npm install -g @my-cli/core
# À la carte (only what I use)
npm install -g @my-cli/core @my-cli/deploy-plugin
# Everything (opt‑in to all plugins)
npm install -g @my-cli/all # metapackage
이점
- 설치 용량 감소 (예: 250 MB에서 15 MB)
- 시작 시간 단축
- 각 플러그인의 독립적인 진화
- 더 안전하고 제어된 릴리스
CLI를 마이크로서비스 생태계처럼 다루고 스트랭글러 피그 패턴을 적용하면, 모놀리식 코드베이스를 모듈식·플러그인 기반 아키텍처로 최소 위험·최대 유연성으로 전환할 수 있습니다.