Skip to content

多业务线前端架构:业务分离与能力复用的边界 ​

在不断扩展的管理后台里,新业务往往从旧业务中“长”出来:筛选条件大体相同,表格字段暂时一致,接口甚至可以复用。最省事的做法,是给旧页面加一个业务类型参数,用条件判断切换数据范围。

这种方案上线快,代价却隐蔽:多条业务线被绑进同一个页面,之后的每次修改都可能牵动全局。另一个极端是整页复制、各自维护——隔离是彻底了,但公共 Bug、交互升级和接口变更都要重复处理。

真正要回答的问题,不是“要不要复用代码”,而是:

哪些代码会因为同一个原因变化,哪些代码只是现在看起来相似?

相似不等于稳定 ​

两个报表可能都有日期、渠道、消耗、收入和 ROI,但字段相同不代表语义相同。随着业务演进,它们会逐渐分化出不同的:

  • 筛选条件和默认值
  • 数据权限和菜单权限
  • 汇总口径
  • 下钻层级
  • 导出字段
  • 操作按钮
  • 发布节奏

如果过早把它们抽成一个“通用报表”,这些差异最终会以业务判断的形式渗回代码:

ts
if (isBusinessA) {
  // ...
} else if (isBusinessB) {
  // ...
}

起初只有一个分支,随后扩散到筛选、请求、缓存、弹窗、导出和生命周期。到最后,修改任何一条业务线,都要回归所有业务线。

两种常见的极端 ​

用条件分支复用整页 ​

这条路代码量最小,共享的却是最不稳定的页面编排。典型信号包括:

  • isXxx 判断持续增加
  • 组件的 Props 和 Slots 不断膨胀
  • 公共组件开始认识业务名称
  • 修改单个业务也要执行全链路回归
  • 新需求只能继续堆配置项,无法在业务目录内闭环

到了这一步,共享页面不再降低成本,而是在放大每次变更的影响范围。

整页复制所有代码 ​

复制能快速建立业务边界,但如果连稳定能力也一并复制,问题会换个形式出现:

  • 同一个 Bug 需要修复多次
  • 相同的格式化逻辑逐渐产生差异
  • API 类型和枚举难以保持一致
  • 通用交互升级无法同步
  • 评审时难以分辨:哪些差异来自业务需要,哪些只是版本漂移

“完全独立”不应等于“所有代码都复制”。

推荐方案:业务编排独立,稳定能力共享 ​

更稳妥的结构是:每条业务线拥有独立的页面与应用逻辑,同时复用不含业务含义的底层能力。

text
业务A页面
  └─ 业务A composable
      └─ 公共 API / 组件 / 工具

业务B页面
  └─ 业务B composable
      └─ 公共 API / 组件 / 工具

业务线应当独立拥有的:

  • 路由和页面入口
  • 页面状态
  • 筛选配置
  • 请求参数构造
  • 权限控制
  • 下钻流程
  • 生命周期和错误处理

适合下沉共享的:

  • HTTP Client 和 API 封装
  • DTO、枚举和协议转换
  • 日期、金额、比例等格式化
  • 分页、导出和下载能力
  • 不含业务判断的基础组件
  • 已被多条业务线长期验证一致的列工厂

依赖方向始终保持单向:

text
业务页面 → 业务逻辑 → 公共能力

公共能力不反向依赖业务页面,更不通过业务类型参数决定自身行为。

用“变化原因”判断是否复用 ​

抽取公共代码之前,可以依次问自己:

  1. 两段代码未来会因为同一个原因修改吗?
  2. 字段名称相同,业务语义也完全相同吗?
  3. 两条业务线总是同步发布吗?
  4. 修改一方时,另一方理应跟着回归吗?
  5. 抽取后是否需要传入业务类型参数?
  6. 公共模块能否在不知道业务名称的前提下工作?

多数答案为否,就应当保持分离。

判断标准可以收敛为一句话:

按变化原因分离,按稳定程度复用。

同一个原因变化的代码放在一起,不同原因变化的代码分开——这比追求更少的文件和代码行更可靠。

允许暂时重复 ​

重复并不总是坏事。面对尚未稳定的新业务,渐进式抽象往往更稳妥:

  1. 第一条业务线按自身需求实现。
  2. 第二条业务线允许适量重复。
  3. 观察两边经历数次真实需求后的演化方向。
  4. 只抽取被证明长期一致的部分。
  5. 一旦共享模块开始出现业务开关,重新检查边界。

短期重复的成本可见、可估算;错误抽象带来的耦合,则会在之后的每一次需求中持续付息。

与 Java / Spring 分层的对应 ​

对 Java 开发者来说,这个结构并不陌生——它接近 Controller 与 Application Service 的分层习惯:

text
BusinessAController → BusinessAService
BusinessBController → BusinessBService
                         ↓
               SharedClient / Converter / Utility

两条业务线可以共用 Feign Client、DTO Converter 和工具类,但很少有人会为了减少类的数量,把两个 Application Service 合并成一个布满业务类型判断的“大 Service”。前端页面与 composable 的边界,本质上是同一个问题。

最后的取舍 ​

架构设计的目标不是消除重复,而是控制变化:

  • 业务能够独立演进
  • 修改范围容易预测
  • 回归边界清晰
  • 稳定能力只维护一份
  • 公共模块不感知具体业务

当“复用”开始要求公共代码理解业务差异时,选择分离;当多条业务线反复修复完全相同的问题时,才说明其中存在值得下沉的稳定能力。

最理想的平衡不是最少的代码,而是:

让业务自由变化,让稳定能力持续复用。

内容均为个人学习与公开总结,不含未脱敏的内部资料。