现有架构的问题
1. 58App 架构
现有架构存在如下几个问题:
- 编译速度慢
- 业务线无法独立开发、调试
- 耦合严重,多平台组件复用难度大
- 重构、替换实现成本高
- 测试范围边界难以界定
- 需求需要改动多模块
- 厂商包、极速包改造难度大
- 业务组件间通信只能异步 (walle)
2. SDK 架构
包含公司级 SDK、APP 工厂 SDK、业务中间件 SDK
现有架构存在如下几个问题:
- 对外 API 与实现耦合,无法快速实现替换,重构难度大
- 对外 API 范围未约束,测试时无法界定测试范围
- 增加迭代过程中的向下兼容成本
彻底的组件化设计
可以比较流行的框架 Fresco 的分包设计:
特点:
接口与实现分离,符合面向对象的开闭、依赖倒置原则,高层模块不依赖低层模块,两者都依赖其抽象;抽象不依赖细节,细节依赖抽象
1. 方案设计
(1) 新架构图
(2) 依赖关系图
(3) 组件结构图
一个独立的组件工程包含如下的结构:
- 依赖关系: 实现库依赖 api 库,demo 依赖 api 库 & 实现库,demo 中具体业务只能从 api 库中调用相关能力
- api 库: 包含对外的接口、共用的资源、view 以及工具类,工具类中除了包含普通工具外,还需要提供页面跳转与接口实例获取能力
- 实现库: 依赖于 api 库,实现对外暴露的接口
(4) 解耦方式
- 页面跳转解耦: 路由框架
- 组件间通信解耦: 服务暴露组件,依赖注入实现,做到同步通信
- 业务组件如何管理 Application: 使用 Application 分发管理框架
- 依赖注入如何实现作用域配置: WBRouter 路由框架的依赖注入每次都是新对象,Hilt 更加灵活,可以通过注解配置作用域:单例、Activity 作用域、Fragment 作用域、View 作用域,但 Hilt 框架过重,可以对 WBRouter 进行扩展
- 通用组件初始化: 最上层进行统一初始化,下层业务只通过 api 进行调用
(5) 支持快速发布 aar
aar 要求:备份、名称规则
- 本地发布测试 aar
- ici 发布正式的 aar
(6) 支持两种调试方式
- 独立运行,走自己独立的测试
- 嵌入运行,基于复合构建的方式,快速替换主工程里的 aar 依赖
(7) 测试的标准
- api 库变动时,所有的依赖库都要测试
- lib 库变动时,只需要独立功能测试
2. demo
以下组件均为独立工程,拥有 demo,可直接运行,上图为复合构建模式视图
project | 描述 |
58AppProject | 打包工程 |
58HouseLib | 房产业务组件 |
58JobLib | 招聘业务组件 |
WubaBasicBussinessLib | 基础业务组件 |
WubaCommonsLib | 基础业务组件 |
WubaShareSDK | App 工厂组件 |
PassportSDK | 集团基础组件 |
(1) 效果图
(2) demo 架构图
(3) 代码示例
打包工程依赖:
dependencies {
// 基础组件 - passport sdk
implementation 'com.wuba.sdk:passport-api:1.0.1'
implementation 'com.wuba.sdk:passport-lib:1.0.1'
// APP 工厂 - 分享 sdk
implementation 'com.wuba.wuxian.sdk:share-api:1.0.0'
implementation 'com.wuba.wuxian.sdk:share-lib:1.0.0'
// 业务基础组件 - commons lib
implementation 'com.wuba.wuxian.lib:commons-api:1.0.2'
implementation 'com.wuba.wuxian.lib:commons-lib:1.0.2'
// 业务基础组件 - basic business lib
implementation 'com.wuba.wuxian.lib:basicbussiness-api:1.0.1'
implementation 'com.wuba.wuxian.lib:basicbussiness-lib:1.0.1'
// 业务组件 - house lib
implementation 'com.wuba.wuxian.lib:house-api:1.0.0'
implementation 'com.wuba.wuxian.lib:house-lib:1.0.0'
// 业务组件 - job lib
implementation 'com.wuba.wuxian.lib:job-api:1.0.0'
implementation 'com.wuba.wuxian.lib:job-lib:1.0.0'
}
58JobLib 中 api 库依赖:
dependencies {
// 只依赖路由框架,路由跳转与依赖注入使用
kapt "com.wuba.wuxian.sdk.wbrouter:compiler:${WBRouterVersion}"
api "com.wuba.wuxian.sdk.wbrouter:core:${WBRouterVersion}"
}
58JobLib 中 lib 库依赖:
dependencies {
// 依赖 job api 库
implementation project(':job-api')
// 依赖组件 api
implementation 'com.wuba.sdk:passport-api:1.0.1'
implementation 'com.wuba.wuxian.sdk:share-api:1.0.0'
implementation 'com.wuba.wuxian.lib:commons-api:1.0.1'
implementation 'com.wuba.wuxian.lib:basicbussiness-api:1.0.1'
}
招聘业务组件调用登陆功能 (passport-api):
LoginClient.launch(this)
passport-api 相关接口定义:
object LoginClient {
// 通过 ARouter 获取注入的接口例
private fun getLoginClientService(context: Context): ILoginClientService? {
val loginClientService = WBRouter.navigation(context, PassportRouters.LOGIN_CLIENT_ROUTER)
return if (loginClientService != null) loginClientService as ILoginClientService else null
}
@JvmStatic
fun register(context: Context, callback: LoginCallback?) {
getLoginClientService(context)?.register(callback)
}
@JvmStatic
fun unregister(context: Context, callback: LoginCallback?) {
getLoginClientService(context)?.unregister(callback)
}
@JvmStatic
fun onLoginCallback(context: Context) {
getLoginClientService(context)?.onLoginCallback()
}
@JvmStatic
fun launch(context: Context) {
getLoginClientService(context)?.launch(context)
}
@JvmStatic
fun logoutAccount(context: Context) {
getLoginClientService(context)?.logoutAccount(context)
}
@JvmStatic
fun isLogin(context: Context): Boolean {
return getLoginClientService(context)?.isLogin(context) ?: false
}
@JvmStatic
fun getPPU(context: Context): String {
return getLoginClientService(context)?.getPPU(context) ?: ""
}
@JvmStatic
fun getUserID(context: Context): String {
return getLoginClientService(context)?.getUserID(context) ?: ""
}
}
passport-lib 相关实现类:
// 自动注入
@Route(PassportRouters.LOGIN_CLIENT_ROUTER)
class LoginClientService : ILoginClientService {
override fun register(callback: LoginCallback?) {
// ...
}
override fun unregister(callback: LoginCallback?) {
// ...
}
override fun onLoginCallback() {
// ...
}
override fun launch(context: Context) {
// ...
}
override fun logoutAccount(context: Context) {
// ...
}
override fun isLogin(context: Context): Boolean {
// ...
}
override fun getPPU(context: Context): String? {
// ...
}
override fun getUserID(context: Context): String? {
// ...
}
}
可以看到,组件间交互都是通过 api 层,lib 层通过 ARouter 自动注入,当然需要对 ARouter 进行扩展支持注入对象生命周期管理,使用 ARouter 可以方便的通过路由获取 Activity、Fragment、接口实现等
api 层除了包含对外的 api、公用工具类、页面跳转方法、接口实例获取方法外,还可以包含公用的资源、View 等。lib 层包含具体的实现、个性化的定制等
3. 收益
- 加快编译速度
每个业务功能都是一个单独的工程,可独立编译运行,拆分后代码量较少,编译自然变快,如果能推动业务线进行改造,业务线编译速度预计可提升 50% 以上
- 增强组件复用能力
组件类似我们引用的第三方库,只需维护好每个组件,一建引用集成即可。业务组件可上可下,灵活多变;而基础组件,为新业务随时集成提供了基础,减少重复开发和维护工作量。在多 APP 垂直共用业务越来越多,集团新 APP 快速涌出的挑战下,开发、迭代效率预计提升 30% 以上
- 提高协作效率
解耦使得组件之间彼此互不打扰,组件内部代码相关性极高。 团队中每个人有自己的责任组件,不会影响其他组件;降低团队成员熟悉项目的成本,只需熟悉责任组件即可
- 提高QA测试效率,降低风险
对测试来说,只需重点测试改动的组件,而不是全盘回归测试,测试效率预计可提升 20% - 30%
- 灵活多变、快速重构
不同 APP 拥有个性化实现,厂商包、极速包严控包大小,面向接口编程,快速灵活响应,替换底层第三方库无需修改上层业务
为了更直观地阐述收益点,我们列出下具体的业务场景:
# | 场景 | 收益 |
1 | 编译速度 | 对于业务线、业务相关组件编译速度将大幅提升,预计提升 50% 以上 |
2 | 提升已有垂直共用业务迭代效率 | 垂直业务库维护成本降低,开发、调试、迭代效率提升 30% 以上,如部落、房产、本地版垂直库 |
3 | 快速响应新的垂直业务接入 | 如去年本地版接入房产业务,后续此类场景可做到快速响应 |
4 | 新 App 快速集成 | 58到家等新 App 快速集成 |
5 | 提升 App 工厂迭代效率 | aar 发布模式优化可快速定位问题,API 库对于向下兼容、旧有代码清理、单元测试等都有收益,而 API 与实现分离可以做到代码重构、底层库替换成本最低 |
6 | 提升公司 SDK 迭代效率 | 参考App 工厂 |
7 | 提升 QA 测试效率 | API 层与实现层,快速界定是否需要上层业务回归测试,缩小测试范围,减少隐藏 bug 风险 |
8 | 厂商包、极速包 | 对于包大小控制严格的厂商包、极速包可快速删减功能、替换实现 |
9 | 个性化业务 | 不同 APP 可根据 API 层快速定制个性化业务 |
10 | 精简包大小 | 降低删除无用代码、资源风险,减少重复功能代码 |
11 | API 变动监控 | API 分离后可通过脚本快速监控变化 |
4. 缺点
- 改造好后,如何持续按规范维护
- 改造成独立的组件后,通用库版本号如何在各组件项目、平台项目迭代过程中保持统一
- 接口与实现隔离,排查问题时无法快速关联实现类定位,如断点
- 底层组件升级时,按照什么标准决定上层使用业务需要同步发布 aar 或者测试
- 增加编码工作量与类数量,对外功能、跳转功能、接口实例都需要编写对应接口与工具类
5. 推进难点
(1) App工厂 SDK
- 多达十几个,开发工作量大
- App工厂 SDK 大多属于基础能力,对外 API、视图、工具众多,API 梳理难度大,如 Hybrid、RN 这种重型的框架
- 测试工作量大:App工厂 SDK 在同城、本地版、安居客、58到家、驾校 App 等都有使用,进行大规模重构势必涉及到多平台共同测试,否则后期使用方平台无法升级迭代
优点是:内部维护,拥有绝对的主动权
(2) 公司级 SDK
- 具备 App 工厂 SDK 的所有难点
- 由 TEG 负责,有跨部门成本,需要很高的收益点和数据进行推动
(3) 基础业务组件
- 对外 api 多,梳理成本高
- 使用业务较多,多 app 间存在个性差异
- 重业务,解耦不彻底
优点为目前部分库还未进行 SDK 化改造,属于计划中的任务,同时属于内部维护
(4) 垂直库
- 依赖于底层库的组件化改造
- 重业务,解耦不彻底
- 功能复杂,依赖的组件多,改造工作量大
- 涉及的平台较多,需要同时测试
优点是:业务功能独立,对外接口较少,内部维护
(5) 业务线
- 依赖于底层库的组件化改造
- 有跨部门成本,需要很高的收益点和数据进行推动
- 功能复杂,依赖的组件多,改造工作量大
建议:逐个突破,新老框架并存过度。可以从基础业务组件作为入口 (原本在改造计划中,只是按照新模式进行改造),第二改造优先级为部落垂直库,第三改造优先级为 App 工厂 SDK,最后再以收益数据向业务线、公司级 SDK 进行推广