现有架构的问题

1. 58App 架构


58同城 架构平台部 58同城组织架构图_App

现有架构存在如下几个问题:
  • 编译速度慢
  • 业务线无法独立开发、调试
  • 耦合严重,多平台组件复用难度大
  • 重构、替换实现成本高
  • 测试范围边界难以界定
  • 需求需要改动多模块
  • 厂商包、极速包改造难度大
  • 业务组件间通信只能异步 (walle)

58同城 架构平台部 58同城组织架构图_App_02

2. SDK 架构

包含公司级 SDK、APP 工厂 SDK、业务中间件 SDK


58同城 架构平台部 58同城组织架构图_apache_03

现有架构存在如下几个问题:
  • 对外 API 与实现耦合,无法快速实现替换,重构难度大
  • 对外 API 范围未约束,测试时无法界定测试范围
  • 增加迭代过程中的向下兼容成本

彻底的组件化设计

可以比较流行的框架 Fresco 的分包设计:


58同城 架构平台部 58同城组织架构图_apache_04

特点:

接口与实现分离,符合面向对象的开闭、依赖倒置原则,高层模块不依赖低层模块,两者都依赖其抽象;抽象不依赖细节,细节依赖抽象

1. 方案设计

(1) 新架构图


58同城 架构平台部 58同城组织架构图_apache_05

(2) 依赖关系图


58同城 架构平台部 58同城组织架构图_android_06

(3) 组件结构图

一个独立的组件工程包含如下的结构:


58同城 架构平台部 58同城组织架构图_App_07

  • 依赖关系: 实现库依赖 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


58同城 架构平台部 58同城组织架构图_58同城 架构平台部_08

以下组件均为独立工程,拥有 demo,可直接运行,上图为复合构建模式视图

project

描述

58AppProject

打包工程

58HouseLib

房产业务组件

58JobLib

招聘业务组件

WubaBasicBussinessLib

基础业务组件

WubaCommonsLib

基础业务组件

WubaShareSDK

App 工厂组件

PassportSDK

集团基础组件

(1) 效果图


58同城 架构平台部 58同城组织架构图_android_09

(2) demo 架构图


58同城 架构平台部 58同城组织架构图_App_10

(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

  1. 多达十几个,开发工作量大
  2. App工厂 SDK 大多属于基础能力,对外 API、视图、工具众多,API 梳理难度大,如 Hybrid、RN 这种重型的框架
  3. 测试工作量大:App工厂 SDK 在同城、本地版、安居客、58到家、驾校 App 等都有使用,进行大规模重构势必涉及到多平台共同测试,否则后期使用方平台无法升级迭代

优点是:内部维护,拥有绝对的主动权

(2) 公司级 SDK

  1. 具备 App 工厂 SDK 的所有难点
  2. 由 TEG 负责,有跨部门成本,需要很高的收益点和数据进行推动

(3) 基础业务组件

  1. 对外 api 多,梳理成本高
  2. 使用业务较多,多 app 间存在个性差异
  3. 重业务,解耦不彻底

优点为目前部分库还未进行 SDK 化改造,属于计划中的任务,同时属于内部维护

(4) 垂直库

  1. 依赖于底层库的组件化改造
  2. 重业务,解耦不彻底
  3. 功能复杂,依赖的组件多,改造工作量大
  4. 涉及的平台较多,需要同时测试

优点是:业务功能独立,对外接口较少,内部维护

(5) 业务线

  1. 依赖于底层库的组件化改造
  2. 有跨部门成本,需要很高的收益点和数据进行推动
  3. 功能复杂,依赖的组件多,改造工作量大

建议:逐个突破,新老框架并存过度。可以从基础业务组件作为入口 (原本在改造计划中,只是按照新模式进行改造),第二改造优先级为部落垂直库,第三改造优先级为 App 工厂 SDK,最后再以收益数据向业务线、公司级 SDK 进行推广