从单体 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 当作微服务生态系统来对待,并运用绞杀树模式,你可以在最小风险、最大灵活性的前提下,从单体代码库平滑转向模块化、插件驱动的架构。

Back to Blog

相关文章

阅读更多 »

让终端逐像素变得更美

2025年11月13日 我们很高兴宣布对 Gemini CLI 用户体验进行重大升级,使您的终端交互更加稳健、直观,且……