从单体 CLI 到模块化插件:应用 Strangler Fig Pattern
发布: (2025年12月4日 GMT+8 11:41)
6 min read
原文: Dev.to
Source: Dev.to
TL;DR – 安全迁移策略
- 将命令抽取为 外部插件(以 beta 形式发布)
- 在核心(v2.x – 零破坏性更改)中 作为依赖 引入插件
- 收集反馈,迭代,稳定
- 在 v3.0.0 中 移除依赖(破坏性更改,但已做好准备)
- 用户只安装 自己需要的 内容
结果:安全迁移 + 插件独立演进
问题:CLI 如何变得难以维护
你的 CLI 可能是这样起步的:
cli/
├── commands/
│ ├── deploy.js
│ └── status.js
├── utils/
│ └── helpers.js
└── index.js
简洁、清晰、一切都有序。
六个月后:
cli/
├── commands/ (37 files)
├── utils/ (23 files)
├── services/ (15 files)
├── integrations/ (12 files)
└── shared/ (who even knows?)
现在你面临:
- 紧耦合 – 每个命令都会导入 10+ 个工具文件
- 共享状态噩梦 – 所有代码都触及的全局配置
- 发布风险大 – 任意一次改动都需要测试整个 CLI
- 开发缓慢 – 添加功能需要数周的协同工作
- 破坏性更改地狱 – 无法在不破坏整体的情况下演进单个部分
听起来熟悉吗? 你已经构建了一个单体。
后端教训:从单体到模块化
旧方式(单体 API)
所有代码在同一个代码库
↓
紧耦合的领域
↓
统一发布
↓
高风险部署
↓
特性交付慢
现代方式(微服务 / 模块化)
轻量 API 网关
↓
隔离的领域服务
↓
独立部署
↓
版本化契约
↓
快速、安全的迭代
关键洞见: 隔离领域,建立清晰边界,使其能够独立演进。这同样适用于 CLI。
解决方案:核心 + 插件架构
轻量核心
核心应当 平淡且稳定:
core/
├── auth/ // 认证逻辑
├── config/ // 配置管理
├── dispatcher/ // 命令路由
├── utils/ // 横切关注点
└── plugin-loader/ // 插件发现
核心提供基础设施,并让位于业务实现。
插件
每个插件都是一个自包含的领域:
plugins/
├── deploy-plugin/
│ ├── commands/
│ ├── tests/
│ ├── README.md
│ └── package.json // 独立版本控制!
├── logs-plugin/
└── backup-plugin/
每个插件:
- 只承担单一职责
- 拥有自己的语义化版本
- 可独立安装(
npm i @cli/deploy-plugin) - 改动不会影响其他插件
- 在完全隔离的环境中测试
迁移策略:绞杀树(Strangler Fig)模式
绞杀树模式(Martin Fowler)让你能够逐步迁移,避免一次性的大改写风险。
步骤 1 – 以 Beta 形式发布外部插件
# 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
为什么是 beta? 用户可以自行选择测试,你可以快速迭代,破坏性更改是可以预期的。
// 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"
}
}
步骤 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→ 插件自动随之安装 - 对已有用户没有破坏性更改
- 插件对早期采用者仍可单独使用
步骤 3 – 逐步迁移命令
v2.5.0(初始 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
每一步都安全发布,用户感受不到差异,插件在真实环境中逐渐成熟。
步骤 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
}
}
给用户的迁移指南
# 旧方式 (v2.x) – 所有内容都打包
npm install -g @my-cli/core
# 新方式 (v3.x) – 按需安装
npm install -g @my-cli/core
npm install -g @my-cli/deploy-plugin # 只在需要部署功能时
npm install -g @my-cli/logs-plugin # 只在需要日志功能时
为何是大版本号: 破坏性更改 – 插件不再自动安装,用户获得更大的控制权、更小的体积以及更快的安装速度。
步骤 5 – 用户按需安装
# 最小化安装(仅核心工具)
npm install -g @my-cli/core
# 按需挑选(只装我用到的)
npm install -g @my-cli/core @my-cli/deploy-plugin
# 全部装(可选全部插件)
npm install -g @my-cli/all # 元包
收益
- 安装体积更小(例如 15 MB vs 250 MB)
- 启动速度更快
- 每个插件可独立演进
- 发布更安全、更可控
通过把 CLI 当作微服务生态系统来对待,并运用绞杀树模式,你可以在最小风险、最大灵活性的前提下,从单体代码库平滑转向模块化、插件驱动的架构。