编程进阶网 编程进阶网
首页
  • 在线工具
  • JSON工具
  • 文本工具
  • 图片处理
  • 文档转化
  • 代码压缩
  • 加解密
  • 时间日期
  • 网络工具
  • 颜色设计
  • 二维码
  • 开发实用
  • 计算机组成原理
  • 操作系统原理
  • 网络协议原理
  • 数据库系统原理
  • 序卷导读
  • 数据本质
  • 运行模型
  • 并发设计
  • 内存真相
  • 交互系统
  • 面向对象
  • 设计原则
  • 设计模式
  • 系统架构
  • 体系建设
  • 代码品质
  • 方案设计
  • 稳定可靠
  • 工程运维
  • 性能优化
  • 数据结构导论
  • 线性结构详解
  • 树哈希结构论
  • 容器设计实战
  • 经典算法思想
  • 工程案例剖析
  • 算法题库精练
  • C语言入门
  • C综合案例
  • C专栏博客
  • C标准集库
  • C++入门教程
  • C++综合案例
  • C++专栏博客
  • C++编程技巧
  • Java入门教程
  • Java综合案例
  • Java专栏博客
  • Go入门教程
  • Go综合案例
  • Go专栏博客
  • Go开发技巧
  • JavaScript入门
  • JavaScript案例
  • JavaScript高级
  • Android库解读
  • Android专栏
  • iOS ObjC入门
  • iOS Swift入门
  • iOS入门精通
  • Web之Html手册
  • Web之TypeScript
  • Web之Vue高级进阶
  • Linux之QML入门
  • Linux之QT核心库
  • Python教程
  • Shell&Bash教程
  • 工具脚本
  • 自动化脚本
  • 质量保障
  • 产品思考
  • 软实力
  • 开发流程
  • Git应用
  • 技术模版
  • 技术规范
  • Markdown
  • Mermaid
  • 开源协议
  • 毛选解读
  • 自我精进
  • 关于我
  • 自我精进
  • 职场管理
  • 职场面试
  • 心情杂货
  • 友情链接

杨充

专注编程 · 终身学习者
首页
  • 在线工具
  • JSON工具
  • 文本工具
  • 图片处理
  • 文档转化
  • 代码压缩
  • 加解密
  • 时间日期
  • 网络工具
  • 颜色设计
  • 二维码
  • 开发实用
  • 计算机组成原理
  • 操作系统原理
  • 网络协议原理
  • 数据库系统原理
  • 序卷导读
  • 数据本质
  • 运行模型
  • 并发设计
  • 内存真相
  • 交互系统
  • 面向对象
  • 设计原则
  • 设计模式
  • 系统架构
  • 体系建设
  • 代码品质
  • 方案设计
  • 稳定可靠
  • 工程运维
  • 性能优化
  • 数据结构导论
  • 线性结构详解
  • 树哈希结构论
  • 容器设计实战
  • 经典算法思想
  • 工程案例剖析
  • 算法题库精练
  • C语言入门
  • C综合案例
  • C专栏博客
  • C标准集库
  • C++入门教程
  • C++综合案例
  • C++专栏博客
  • C++编程技巧
  • Java入门教程
  • Java综合案例
  • Java专栏博客
  • Go入门教程
  • Go综合案例
  • Go专栏博客
  • Go开发技巧
  • JavaScript入门
  • JavaScript案例
  • JavaScript高级
  • Android库解读
  • Android专栏
  • iOS ObjC入门
  • iOS Swift入门
  • iOS入门精通
  • Web之Html手册
  • Web之TypeScript
  • Web之Vue高级进阶
  • Linux之QML入门
  • Linux之QT核心库
  • Python教程
  • Shell&Bash教程
  • 工具脚本
  • 自动化脚本
  • 质量保障
  • 产品思考
  • 软实力
  • 开发流程
  • Git应用
  • 技术模版
  • 技术规范
  • Markdown
  • Mermaid
  • 开源协议
  • 毛选解读
  • 自我精进
  • 关于我
  • 自我精进
  • 职场管理
  • 职场面试
  • 心情杂货
  • 友情链接
  • README
  • 体系建设优化

  • 代码品质工坊

  • 稳定性与可靠性

  • 工程化与运维

  • 方案设计思想

    • README
    • 通用架构设计方案
    • 组件化方案的设计
      • 01.一个协作的灾难
        • 1.1 团队的真实困境
        • 1.2 单体仓库的代价
        • 1.3 组件化的契机
      • 02.要解决的核心矛盾
        • 2.1 编译效率瓶颈
        • 2.2 协作冲突频发
        • 2.3 复用与解耦失衡
        • 2.4 组件化的本质
      • 03.业界主流方案
        • 3.1 三种思路溯源
        • 3.2 横向对比矩阵
        • 3.3 模块化与组件化
      • 04.组件化设计原则
        • 4.1 单向依赖法则
        • 4.2 职责单一边界
        • 4.3 接口下沉策略
        • 4.4 拆分粒度判断
      • 05.组件化架构落地
        • 5.1 整体分层结构
        • 5.2 组件依赖规范
        • 5.3 组件拆分决策
      • 06.组件间通信方案
        • 6.1 业务调用案例
        • 6.2 三种通信方式
        • 6.3 路由方案落地
        • 6.4 SPI 服务发现
        • 6.5 事件总线场景
        • 6.6 选型对比矩阵
      • 07.组件化常见陷阱
        • 7.1 过度拆分反例
        • 7.2 循环依赖反例
        • 7.3 接口爆炸反例
        • 7.4 版本治理反例
      • 08.组件化演进路线
        • 8.1 V1 单体起步
        • 8.2 V2 模块化分包
        • 8.3 V3 组件化拆分
        • 8.4 V4 组件治理
      • 09.总结与决策
        • 9.1 拆分检查清单
        • 9.2 选型决策树
    • SDK设计与发布方案
    • 缓存架构设计思想
    • 数据库SQL设计思想
    • 分库分表方案设计
    • 分布式ID生成方案
    • 消息队列方案选型
    • 长链接方案的设计
    • 认证授权方案设计
    • API网关设计方案
    • 路由库设计思想
    • 网络检测方案设计
    • 幂等性设计方案
    • 分布式锁方案设计
    • 限流熔断方案设计
    • 移动端防抓包实践
    • 通用轮训方案设计
    • 状态机设计的思想
  • 性能优化实践

  • 真经
  • 方案设计思想
杨充
2025-02-19
目录

组件化方案的设计

# 02.组件化方案的设计

本篇定位:架构(01 篇)解决的是"代码该写在哪一层",组件化解决的是"代码该写在哪一个仓库 / 模块"。本文从一个真实的协作灾难讲起,回答三个问题——为什么要做组件化?业界三家做法怎么选?拆到什么粒度才算合理?

# 目录介绍

  • 01.一个协作的灾难
    • 1.1 团队的真实困境
    • 1.2 单体仓库的代价
    • 1.3 组件化的契机
  • 02.要解决的核心矛盾
    • 2.1 编译效率瓶颈
    • 2.2 协作冲突频发
    • 2.3 复用与解耦失衡
    • 2.4 组件化的本质
  • 03.业界主流方案
    • 3.1 三种思路溯源
    • 3.2 横向对比矩阵
    • 3.3 模块化与组件化
  • 04.组件化设计原则
    • 4.1 单向依赖法则
    • 4.2 职责单一边界
    • 4.3 接口下沉策略
    • 4.4 拆分粒度判断
  • 05.组件化架构落地
    • 5.1 整体分层结构
    • 5.2 组件依赖规范
    • 5.3 组件拆分决策
  • 06.组件间通信方案
    • 6.1 业务调用案例
    • 6.2 三种通信方式
    • 6.3 路由方案落地
    • 6.4 SPI 服务发现
    • 6.5 事件总线场景
    • 6.6 选型对比矩阵
  • 07.组件化常见陷阱
    • 7.1 过度拆分反例
    • 7.2 循环依赖反例
    • 7.3 接口爆炸反例
    • 7.4 版本治理反例
  • 08.组件化演进路线
    • 8.1 V1 单体起步
    • 8.2 V2 模块化分包
    • 8.3 V3 组件化拆分
    • 8.4 V4 组件治理
  • 09.总结与决策
    • 9.1 拆分检查清单
    • 9.2 选型决策树

# 01.一个协作的灾难

# 1.1 团队的真实困境

某 App 团队从 2018 年发展到 2022 年,代码量从 30 万行涨到 180 万行,团队从 8 人扩到 45 人。业务还是那些业务,但每次发版越来越痛苦:

  • 冷编译耗时:从最初的 90 秒涨到 18 分钟,开发改一行代码也要等 4 分钟增量编译
  • 代码冲突:每周平均出现 12 次合并冲突,最严重的一次有 7 个团队改同一个 BaseActivity
  • 回归成本:一个支付组的小改动,常常引发首页崩溃,全量回归测试要 2 天
  • 新人上手:新员工平均需要 3 周才能独立提交代码

# 1.2 单体仓库的代价

把单体仓库的痛点解构一下:

这就是为什么"组件化"会成为大型 App 团队的必经之路——它解决的不是代码层面的问题,而是组织层面的问题。

# 1.3 组件化的契机

那个团队最终下决心做组件化的真正契机是:公司要孵化一个新 App,希望复用主 App 80% 的核心能力。结果发现核心能力全部和主 App 的业务代码混在一起,根本抽不出来。

这是一个非常典型的"被业务推着改"的故事——组件化往往不是技术选择,而是业务发展的必然。

# 02.要解决的核心矛盾

# 2.1 编译效率瓶颈

为什么单体工程的编译时间会指数级增长?因为编译器需要为每次改动都做"全图依赖分析"。代码量翻倍,依赖图边数可能翻 4 倍。

组件化的破解:把代码拆成多个独立编译单元(aar / framework / npm 包),改动一个组件只编译这一个组件,其他直接用缓存。

# 2.2 协作冲突频发

3 个人改同一个 BaseActivity 必然会冲突。组件化通过"物理隔离"消除冲突——每个组件一个仓库(或一个目录),每个团队只在自己的组件里写代码。

# 2.3 复用与解耦失衡

复用和解耦看似一对,其实是矛盾的。

  • 强复用 意味着大量代码被多处依赖,一改全身动 → 高耦合
  • 强解耦 意味着各模块互不依赖 → 难以复用

组件化通过接口下沉化解:把可复用的"能力"抽到下层基础组件(强复用),上层业务组件通过接口依赖(强解耦)。

# 2.4 组件化的本质

组件化 = 用物理边界(仓库 / 模块)固化逻辑边界(职责 / 依赖)

它解决的是大团队、大代码库下的组织复杂度和演进成本问题。如果团队 < 10 人或代码 < 30 万行,组件化的收益不如其成本。

# 03.业界主流方案

# 3.1 三种思路溯源

思路一:模块化(Module-based) 最早出现于 90 年代的桌面软件。把代码按"功能领域"分包(如 user/order/payment),每个包独立编译。代表:Java 的 module、Android 的 library module。重点是分包,不强调独立发布。

思路二:组件化(Component-based) 2014 年前后由阿里巴巴、美团等大型 App 团队推动。每个业务组件独立成一个仓库,可以独立编译、独立测试、独立发布 aar/framework。代表:阿里 ARouter、美团 WMRouter。重点是独立发布 + 路由通信。

思路三:微前端(Micro-frontend) 2016 年由 ThoughtWorks 提出,把后端微服务的思想搬到前端。每个子应用是独立技术栈、独立运行时、独立部署。代表:qiankun、single-spa、Module Federation。重点是运行时隔离 + 独立部署。

每一代方案都比前一代隔离得更彻底,但成本也更高。

# 3.2 横向对比矩阵

维度 模块化 组件化 微前端
隔离粒度 分包 独立 aar/framework 独立运行时
是否独立发布 ❌ ✅ ✅
是否独立部署 ❌ ❌(仍是同一个 App) ✅
技术栈是否统一 必须统一 必须统一 可不同
通信成本 极低(直接调用) 中(路由 / SPI) 高(postMessage / Custom Event)
接入成本 低 中 高
典型场景 中小型 App / 后端 大型 App 大型 Web / 多团队 SaaS
代表方案 Gradle module ARouter / WMRouter qiankun / Module Federation

# 3.3 模块化与组件化

很多团队搞不清楚模块化和组件化的区别。一句话:

模块化偏复用,组件化偏解耦 + 独立发布

维度 模块化 组件化
关注点 代码逻辑划分 代码 + 编译 + 发布的全方位隔离
粒度 较粗(按业务领域) 较细(按可独立交付的能力)
典型形态 一个仓库下多个 module 多个仓库 + 中央壳工程
能否独立运行 不能 可以单独跑成"组件壳"

关键判断:如果你的团队还在为"代码该放哪个目录"纠结,先做模块化;如果已经为"编译慢 + 协作冲突"头疼,再上组件化。

# 04.组件化设计原则

# 4.1 单向依赖法则

组件化最容易踩的坑是循环依赖。一旦组件 A 依赖 B、B 依赖 A,独立编译就成了笑话。

铁律:业务组件之间永远不能直接依赖,必须通过公共接口层或路由间接调用。

# 4.2 职责单一边界

每个组件应该只为"一类业务变化"负责。判断标准:如果这个组件需要改动,触发它改动的团队是不是同一个?

反例 问题 正确做法
"通用业务组件"包含登录 + 支付 + 个人中心 三个团队都要改它,高频冲突 拆成 3 个独立组件
"工具组件"既有日期工具又有网络工具 改网络要发布工具组件,影响所有使用方 工具按领域拆分

# 4.3 接口下沉策略

业务组件 A 想调用业务组件 B 的能力,怎么办?不要让 A 依赖 B,而是把 B 提供的能力抽成接口下沉到公共层。

好处:A 不再知道 B 的存在,可以独立编译;B 即使被替换为 B2,A 也无感知。

# 4.4 拆分粒度判断

拆得太粗 → 组件化等于没做;拆得太细 → 维护成本爆炸。判断标准:

信号 说明 应该拆
不同团队负责 两块代码归属不同团队 ✅ 必须拆
不同发布节奏 A 要日发,B 是季发 ✅ 必须拆
不同业务变化 改 A 的需求和改 B 的需求来自不同业务 ✅ 推荐拆
可独立复用 B 可以拿到另一个 App 复用 ✅ 推荐拆
依赖关系清晰 A 和 B 之间几乎不互调 ✅ 推荐拆
代码量都很小 A 和 B 加起来才 200 行 ❌ 不要拆

# 05.组件化架构落地

# 5.1 整体分层结构

为什么这样分层:

  • App 壳:薄薄一层,只做"路由注册 + 启动初始化",不写业务
  • 业务组件层:彼此不直接依赖,通过接口层间接调用
  • 接口层:所有跨组件调用的契约,必须最稳定
  • 通用组件层:业务无关、可在任意 App 复用
  • 基础设施层:平台/语言级能力

# 5.2 组件依赖规范

依赖方向 是否允许 说明
应用层 → 业务组件层 ✅ 主壳组装业务
业务组件层 → 业务组件层 ❌ 必须通过接口层
业务组件层 → 接口层 ✅ 跨组件调用走这里
业务组件层 → 通用组件层 ✅ 复用通用能力
通用组件层 → 业务组件层 ❌ 严格反向,否则复用没意义
通用组件层 → 通用组件层 ⚠️ 谨慎,避免循环

把这张表写进 CI 检查脚本(如 dependency analyzer),自动拦截违规依赖,是保证组件化不腐坏的关键。

# 5.3 组件拆分决策

# 06.组件间通信方案

# 6.1 业务调用案例

来看一个真实场景,便于后续讨论选型:

用户在首页组件 A 看到一个商品,点击购买。系统需要:

  1. A 调用支付组件 C 发起支付流程
  2. 支付组件 C 调用用户组件 B 检查实名认证状态
  3. 支付组件 C 调用订单组件 D 创建订单
  4. 订单组件 D 完成后通知首页组件 A 刷新角标

这个场景同时涉及"页面跳转""能力调用""跨组件通知",非常典型。

# 6.2 三种通信方式

业界主流的组件间通信只有三种:

方式 用途 典型代表
路由(Router) 页面跳转 + 携带参数 ARouter / WMRouter / Vue Router
服务发现(SPI) 调用其他组件的"能力" Java SPI / ARouter Service
事件总线(EventBus) 一对多的通知 EventBus / RxBus / LiveEventBus

核心原则:三者各司其职,不要混用。最常见的反例是用 EventBus 处理本该用 SPI 的能力调用,导致依赖关系完全失控。

# 6.3 路由方案落地

路由解决"我要跳到另一个组件的页面"。本质是用字符串 URL 替代类引用,从而切断编译期依赖。

// 注册(在组件 B 中)
@Route(path = "/user/profile")
class ProfileActivity { ... }

// 调用(在组件 A 中,A 不需要依赖 B)
Router.navigate("/user/profile", { userId: "123" })

关键优势:A 完全不知道 ProfileActivity 在哪里,B 可以随意改实现。

# 6.4 SPI 服务发现

SPI 解决"我要调用另一个组件的能力(不跳页面)"。本质是接口定义在公共层,实现注册到全局服务表。

// 接口(在公共接口层)
interface IUserService {
  isVerified(userId: string): boolean;
}

// 实现(在用户组件 B)
@Service
class UserServiceImpl implements IUserService { ... }

// 调用(在支付组件 C,C 只依赖接口层)
const userService = ServiceRegistry.resolve<IUserService>("IUserService");
const verified = userService.isVerified("123");

关键优势:调用方只依赖接口,运行时才绑定实现,可以做 Mock、可以热替换。

# 6.5 事件总线场景

EventBus 解决"我做完一件事,要通知所有关心它的组件"。本质是发布订阅模式。

// 订阅(在首页组件 A)
EventBus.on("ORDER_CREATED", (orderId) => refreshBadge());

// 发布(在订单组件 D)
EventBus.emit("ORDER_CREATED", orderId);

适用场景:一对多通知、解耦弱关系。 绝对禁忌:不要用 EventBus 做"请求-响应"——例如 A emit("getUser") 然后等 B emit("getUserResp")。这种用法 3 个月后就会变成"事件流满天飞"的灾难。

# 6.6 选型对比矩阵

场景 路由 SPI EventBus
页面跳转 ✅ 首选 ❌ ❌
同步调用能力 ❌ ✅ 首选 ❌
异步广播通知 ❌ ❌ ✅ 首选
一对一精确调用 ⚠️ ✅ ❌
一对多通知 ❌ ❌ ✅
是否阻塞调用方 是 是 否
是否需要返回值 否 是 否

回到 6.1 的真实场景:

步骤 选用方式 原因
1. A 调用 C 发起支付 路由 是页面跳转
2. C 调用 B 查实名 SPI 是同步能力调用,需要返回值
3. C 调用 D 创建订单 SPI 同上
4. D 通知 A 刷新角标 EventBus 一对多广播,A 不阻塞 D

# 07.组件化常见陷阱

# 7.1 过度拆分反例

反例:某 30 万行代码的 App 被拆成 87 个组件,平均每个组件 3500 行。

问题:

  • 一个简单业务需求要改 5 个组件,组件间通信代码比业务代码还多
  • CI 跑 87 个组件的发布流水线,发版从 30 分钟变成 2 小时
  • 新人光看组件依赖图就要 1 周

教训:组件数量应该和团队数量匹配。1 个组件 ≈ 1 个团队 ≈ 1 万 ~ 3 万行代码是相对舒服的尺度。

# 7.2 循环依赖反例

反例:用户组件依赖订单组件查"用户订单数",订单组件依赖用户组件查"用户信息"。

问题:编译时就报错,无法独立发布任何一个。

正确做法:

两个接口都下沉到接口层,业务组件之间永远只通过接口层互调。

# 7.3 接口爆炸反例

反例:团队规定"所有跨组件调用必须走 SPI",结果接口层堆了 240 个接口,新人完全不知道有什么能力可用。

教训:接口层也需要治理:

  • 按"领域"分包(user / order / payment / common)
  • 接口必须有 README,写清楚谁实现、谁调用
  • 已废弃的接口要明确 @Deprecated 并给出迁移路径

# 7.4 版本治理反例

反例:30 个组件各自发版本,主壳工程的依赖文件长达 300 行版本号,经常出现"组件 A 的 1.2 版本和组件 B 的 3.5 版本不兼容"。

正确做法:引入 **BOM(Bill of Materials)**机制——主壳只声明一个 BOM 版本,BOM 里固定一组互相兼容的组件版本。这是 Spring 和阿里开源系列普遍采用的方案。

# 08.组件化演进路线

组件化不是一次性工程,而是 4 个阶段的渐进改造。下面是开篇那个 45 人团队的真实演进路径。

# 8.1 V1 单体起步

规模:8 人团队、30 万行代码、1 个仓库

结构:

痛点:暂时没有,编译 90 秒可接受。 何时该升级:编译时间 > 5 分钟、团队 > 12 人。

# 8.2 V2 模块化分包

规模:18 人团队、80 万行代码、1 个仓库 + 多 module

结构:

做了什么:抽出基础 module(UI / 网络 / 工具),但业务还在一个大 module 里。 带来的收益:基础设施跨项目复用、编译时间降到 8 分钟。 何时该升级:业务还在打架、编译时间又涨到 15 分钟、有跨 App 复用需求。

# 8.3 V3 组件化拆分

规模:35 人团队、140 万行代码、N 个仓库

结构:

做了什么:业务拆成 4 个独立仓库,引入路由 + SPI + EventBus 三件套。 带来的收益:4 个团队完全并行、编译降到 3 分钟(独立组件 50 秒)、新 App 用 2 周就能搭起来。 带来的痛:通信成本上升、版本治理成为问题。

# 8.4 V4 组件治理

规模:45 人团队、180 万行代码、12 个组件仓库

做了什么:

  • 引入 BOM 统一版本
  • 接口层按领域分包并强制 README
  • CI 加上"违规依赖检测",自动拦截
  • 每个组件独立 demo App,可单独运行调试

⚠️ 关键提醒:这条演进路线走完用了 4 年。任何想"一年搞定组件化"的计划都是危险的——业务还在跑,改造必须渐进。

# 09.总结与决策

# 9.1 拆分检查清单

每次决定要不要拆一个组件,对照下面这张清单打分(≥ 3 项为是再拆):

  • [ ] 这块代码归属一个独立的团队
  • [ ] 它有自己独立的发布节奏(如日发 vs 月发)
  • [ ] 它的代码量 > 5000 行且仍在增长
  • [ ] 未来 6 个月有跨 App 复用预期
  • [ ] 它和其他业务的耦合点 ≤ 5 个
  • [ ] 它的接口契约稳定,半年改不超过 2 次

# 9.2 选型决策树

最后一句话:组件化不是技术潮流,是当业务规模逼到你不得不做时才做的事。回到本文开头那个 45 人团队,他们花 4 年走完 V1→V4,真正的胜利不是组件多漂亮,而是孵化新 App 从 6 个月缩短到 6 周。这才是组件化的商业价值。

好的组件化 = 让大团队像小团队一样灵活,让大代码库像小代码库一样好改。

上次更新: 2026/06/28, 17:55:19
通用架构设计方案
SDK设计与发布方案

← 通用架构设计方案 SDK设计与发布方案→

最近更新
01
科学方法实践论法
06-28
02
辩证思维矛盾论法
06-28
03
毛选中的调查观念
06-28
更多文章>
Theme by Vdoing | Copyright © 2019-2026 杨充 | MIT License | 鄂ICP备2024073355号-1 | 鄂ICP备2024073355号
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式