关于app开发:移动端APP组件化架构实践-京东云技术团队

2次阅读

共计 11574 个字符,预计需要花费 29 分钟才能阅读完成。

前言

对于 中大型 挪动端 APP 开发来讲,组件化 是一种罕用的我的项目架构形式。集体最近几年在工作我的项目中也始终应用 组件化 的形式来开发,在这过程中也积攒了一些教训和思考。次要是来自在日常开发中应用组件化开发遇到的问题以及和其余开发同学的交换探讨。

本文通过以下问题来介绍组件化这种开发架构的思维和常见的一些问题:

  • 为什么须要组件化
  • 组件化过程中会遇到的挑战和抉择
  • 如何保护一个高质量的组件化我的项目

提醒:本文说的组件化工程是指 Multirepo 应用独立的 git 仓库来治理组件。

组件化能够带来什么

繁多工程架构遇到的问题

在组件化架构之前,传统应用的工程架构次要是以 Monolithic 形式的繁多工程架构,也就是将所有代码放在 单个代码仓库 里治理。繁多工程架构应用了这么多年为什么忽然遇到了问题,这也引入了 APP 我的项目开发的一个大背景,现有 中大型 APP我的项目变的越来越简单:

  • 多 APP 我的项目并存  – 团体外部存在多个APP 我的项目,不同 APP 心愿能够 复用 现有组件能力疾速搭建出新的APP
  • 性能增多 – 随着我的项目性能越来越多,代码量增多。同时须要更多的开发人员参加到我的项目中,这会减少开发团队之间合作的老本。
  • 多语言 / 多技术栈  – 引入了更多的新技术,例如应用一种以上的 跨平台 UI技术用于疾速交付业务,不同的编程语言、音视频、跨平台框架,减少了整个工程的复杂度。

以上这些业务倒退的诉求就给传统 繁多工程架构 形式带来了很多新的技术要求:

工程效率
  • 工程代码量过大会导致 编译速度 迟缓。
  • git 工程提交同时可能带来更多的 git 提交抵触编译谬误
品质问题
  • 如何将 git 提交关联到对应的功能模块需要。发版时进行合规查看防止带入不标准的代码,对整个功能模块 回滚 的诉求。
  • 如何在单仓库中管控这么多开发人员的代码权限,尽可能防止不平安的提交并且限度改变范畴。
更大范畴的组件复用
  • 根底组件从反对 单个 APP 复用到反对 多个APP 复用。
  • 不只是 根底 能力组件,对于 业务 能力组件也须要反对复用。(例如一个页面组件同时在多个 APP 应用)
  • 跨平台容器 须要复用底层组件能力防止反复开发,同时不同跨平台容器 API 须要尽量放弃对立,底层基础设施向 容器化 倒退反对业务 跨 APP复用。
跨技术栈通信
  • 因为页面导航多技术栈混合共存,页面路由须要反对跨技术栈。
  • 跨组件通信 须要反对 跨语言 / 跨技术栈 通信。
更好的解耦
  • 页面解耦。因为页面导航栈混合共存,页面本身不再清晰的晓得上游和上游页面由什么技术栈搭建,所以页面路由须要做到 齐全解耦 隔离技术栈的具体实现。
  • 业务组件间维持松耦合关系,能够灵便 增加 / 移除,基于现有组件能力疾速搭建出不同的 APP。
  • 对于同一个服务或页面能够插件化形式灵便提供多种不同的实现,不同的 APP 宿主也能够提供不同的实现并且提供 A/B 能力。
  • 因为 包体积 限度和不同组件蕴含雷同符号导致的 符号抵触 问题,在复用组件的时候须要尽可能引入 最小依赖准则 升高接入老本。

组件化架构的劣势

基于以上这些问题,当初的组件化架构心愿能够解决这些问题晋升整个交付 效率 和交付 品质

组件化架构通常具备以下长处:

  • 代码复用  – 性能封装成组件更容易 复用 到不同的我的项目中,间接复用能够 进步开发效率。并且每个组件职责繁多应用时会带入最小的依赖。
  • 升高了解复杂度  – 工程拆分为小组件当前,对于组件应用方咱们只须要通过组件对外裸露的 公开 API去应用组件的性能,不须要了解它外部的具体实现。这样能够帮忙咱们更容易了解整个大的我的项目工程。
  • 更好的解耦  – 在传统繁多工程项目中,尽管咱们能够应用设计模式或者编码标准来束缚模块间的 依赖关系 ,然而因为都寄存在繁多工程目录中短少清晰的 模块边界 仍然无奈防止不衰弱的依赖关系。组件化当前能够明确定义须要对外裸露的能力,对于模块间的依赖关系咱们能够进行强束缚限度依赖,更好的做到解耦。对一个模块的增加和移除都会更容易,并且模块间的依赖关系更加清晰。
  • 隔离技术栈  – 不同的组件能够应用不同的 编程语言 / 技术栈 ,并且不必放心会影响到其余组件或主工程。例如在不同的组件内能够自由选择应用KotlinSwift,能够应用不同的跨平台框架,只须要通过标准的形式暴露出 页面路由 或者 服务办法 即可。
  • 独立开发 / 保护 / 公布 – 大型项目通常有很多团队。在传统繁多我的项目集成打包时可能会遇到代码提交 / 分支合并的抵触问题。组件化当前每个团队负责本人的组件,组件能够独立开发 / 保护 / 公布晋升开发效率。
  • 进步编译 / 构建速度 – 因为组件会提前编译公布成二进制库进行依赖应用,相比编译全副源代码能够节俭大量的编译耗时。同时在日常组件开发时只须要编译大量依赖组件,相比繁多工程能够缩小大量的编译耗时和编译谬误。
  • 管控代码权限  – 通过组件化将代码拆分到不同组件git 仓库中,咱们能够更好的管控代码权限和限度代码变更范畴。
  • 治理版本变更  – 咱们通常会应用CocoaPods/Gradle 这类依赖管理工具来治理我的项目中所有的组件依赖。因为每一个组件都有一个明确的版本,这样咱们能够通过 比照APP 不同版本打包时的组件依赖表很清晰的辨认组件版本个性的变更,防止带入不合规的组件版本个性。并且在呈现问题时也很不便通过配置表进行回滚撤回。

提醒:组件化架构是为了解决 繁多工程架构 开发中的问题。如果你的我的项目中也会遇到这些痛点,那可能就须要做 组件化

组件化遇到的挑战

尽管 组件化 架构能够带来这么多收益,但不是只有应用组件化架构就能够解决所有问题。通常来讲当咱们应用一种新的技术计划解决现有问题的时候也会带来一些新的问题,组件化架构 能带来多少收益次要取决于整个工程组件化的品质。那在 组件化架构 中咱们如何去评估我的项目工程的组件化架构品质,咱们须要关注哪些问题。对于软件架构来讲,最重要的就是治理组件实体以及组件间的关系。所以对于组件化架构来讲次要是关注以下三个问题:

  • 如何划分组件的粒度、组件职责边界在哪里?
  • 组件间的依赖关系应该如何治理?
  • 组件间应该应用哪种形式调用和通信?

1. 组件拆分的粒度、组件职责边界在哪里?

某种程度上组件拆分 粒度 也是一种均衡的艺术,咱们须要在 效率 品质 之间找到一种绝对的均衡。组件拆分粒度 太粗 :导致组件间耦合严密,并不能利用更好的复用 / 解耦 / 进步编译速度这些劣势。组件拆分粒度 太细:导致须要保护更多的组件代码仓库、性能变更可能波及多个组件代码的批改 / 公布,这些都会带来额定的老本,同时组件过多也会导致组件依赖查找过程变的更简单更慢。

组件的 职责 也会影响咱们对于组件的拆分形式:每个组件的定位是什么,应该蕴含什么样的性能,是否能够被复用,增加某个性能的时候应该创立新组件还是增加到现有组件,当组件简单到肯定水平时是否须要拆分出新个组件。

在拆分组件前须要提前去思考这些问题。

2. 组件间的依赖关系应该如何治理?

组件间的依赖形式次要分为 间接强耦合依赖 间接松耦合依赖 强耦合依赖 是对依赖的组件间接应用对应的 API 进行调用,这种调用形式长处是简略间接性能更好,毛病是一种齐全耦合的调用形式。(根底组件 通常应用这种形式)。松耦合依赖 次要是通过 告诉 URL SchemeObjC Runtime 服务接口 事件队列 等通信形式进行间接依赖调用。尽管性能绝对差一点,但这是一种绝对耦合水平比拟低并且灵便的依赖形式。(业务组件 通常应用这种形式)

组件间的依赖关系很重要是因为在长期的我的项目开发演化过程中很容易造成一种简单的 网状依赖关系 。尽管看似应用组件化的形式将模块拆分成不同的组件,然而组件间可能存在很多互相穿插的依赖耦合关系,很多组件都被其余组件 间接依赖 隐式间接依赖 。这样咱们就背离了 组件化 架构更好的 解耦 、更好的 复用 、更 疾速 的开发 / 编译 / 公布的初衷。

所以咱们须要制订一套标准去束缚和标准组件间的依赖关系:两个组件之间是否能够依赖,组件间依赖方向,抉择强耦合依赖还是松耦合依赖。

3. 组件间松耦合依赖关系应该应用哪种形式调用和通信?

松耦合依赖 通常能够应用 告诉 URL SchemeObjC Runtime 服务接口 事件队列 等形式通信进行间接调用,然而应用哪种形式更好业界也有很多争执,并且每种形式都有一些优缺点。通常在我的项目中会依据不同的应用场景 至多 会抉择 2 种通信形式。

耦合水平低的形式例如 URL Scheme,能够做到齐全解耦绝对比拟灵便。然而无奈利用 编译时查看 无奈传递简单对象 、调用方 / 被调用方都须要对参数做大量的正确性 检查和对齐。同时可能无奈检测对应的调用办法是否存在。

耦合水平高的形式例如 服务接口 ,须要对服务接口办法进行强依赖,然而能够利用 编译时查看 、传递简单对象、并且能够更好的反对Swift 个性。

咱们须要在 解耦水平 容易应用 平安 上找到一种适合的形式。

提醒:这里的耦合水平高是绝对于耦合水平低的形式进行比拟,相比 间接依赖 对应组件仍然是一种耦合水平低的依赖关系。

组件化架构实际标准和准则

基于以上这些组件化架构的问题,须要一些 组件化架构 相干的标准和准则帮忙咱们做好组件化架构,前面次要会围绕以下三点进行介绍:

  • 组件拆分准则 – 拆分思维和最佳实际领导组件拆分
  • 组件间依赖 – 优化组件间依赖关系跨组件调用 / 通信形式的抉择
  • 品质保障  – 防止在继续的工程演化过程中工程质量逐步劣化。次要蕴含平安卡口和CI 查看

工程实例

接下来以一个典型的 电商 APP架构案例来介绍一个组件化工程。这个案例架构具备之前所说现有中大型 APP 架构的一些特点,多组件、多技术栈、业务间须要解耦、复用底层根底组件。基于这个案例来介绍下面的三点准则。

组件拆分准则

组件拆分最重要是帮咱们梳理出组件职责以及组件职责的边界。组件划分也会应用很多通用的设计准则和架构思维。

应用分层思维拆分

通常咱们能够首先应用分层架构的思维将 所有组件 纵向拆分为 多层组件 ,下面层级的组件只能依赖上面层级的组件。个别至多能够划分为 四层组件

  • 根底层  – 提供外围的与下层业务无关的根底能力。能够被下层组件 间接依赖 应用。
  • 业务公共层  – 次要蕴含页面路由、公共 UI 组件、跨组件通信以及服务接口,可被下层组件 间接依赖 应用。
  • 业务实现层  – 业务外围实现层,蕴含原生页面、跨平台容器、业务服务实现。组件间 不能间接依赖 ,只能通过调用 页面路由 或跨组件 通信组件 进行应用。
  • APP 宿主层  – 次要蕴含 APP 主工程、启动流程、页面路由注册、服务注册、SDK 参数初始化等组件,用于构建打包生成相应的APP

划分层级能够很好的领导咱们进行组件拆分。在拆分组件时咱们须要先辨认它应该在哪一层,它应该以哪种调用形式被其余组件应用,新增加的性能是否会产生 反向依赖 ,帮忙咱们标准组件间的依赖关系。同时按层级拆分组件也有利于底层根底组件的 复用

以下场景应用分层思维就很容易辨认:

根底组件依赖业务组件

例子:APP 内业务发动网络申请通常须要携带 公共参数 /Cookie

  • 没有组件分层束缚  – 网络库可能会依赖 登录服务 获取用户信息、依赖 定位服务 获取经纬度,引入大量的依赖变成业务组件。
  • 有组件分层束缚  – 网络库作为一个 根底组件 ,它不须要关注下层业务须要携带哪些公共业务参数,同时登录 / 定位服务组件在网络库下层不能被 反向依赖 。这时候会思考独自创立一个公共参数治理类,在 APP 运行时监听各种状态的变更并调用网络库更新 公共参数 /Cookie
业务组件间依赖方向是否正确

登录状态切换 常常会波及到很多业务逻辑的触发,例如清空本地用户缓存、地址缓存、清空购物车数据、UI 状态变更。

  • 没有组件分层束缚 – 可能会在登录服务内当登录状态切换时调用多个业务逻辑的触发,导致登录服务引入多个业务组件依赖。
  • 有组件分层束缚 – 登录组件只须要在登录状态切换时发出通知,无需晓得登录状态切换会影响哪些业务。业务逻辑应该监听登录状态的变更。
辨认根底组件还是业务组件

尽管很多场景下咱们很容易能辨认解决进去一个性能应该归属于 根底 组件还是 业务 组件,例如一个 UI 控件是根底组件还是业务组件。然而很多时候边界又十分的含糊,例如一个增加购物车按键应该是一个 根底 组件还是 业务 组件呢。

  • 根底组件 – 如果不须要依赖业务公共层那该当划分为一个根底组件。
  • 业务组件 – 依赖了业务公共层或者网络库,那就应该划分为一个业务组件。

分层思维能够很好的帮忙咱们治理组件间的依赖关系,并且明确每个组件的职责边界。

根底 / 业务组件拆分准则

划分根底 / 业务组件次要是为了强制束缚组件间的依赖关系。以下面的组件分层架构为例:

  • 根底组件  – 根底组件被间接依赖应用,应用方调用根底组件 对外裸露 API间接应用。根底层 业务公共层 都为根底组件。
  • 业务组件  – 业务组件 不可 被间接依赖应用,只能通过 间接通信形式 进行应用。APP 宿主层 业务实现层 都为业务组件。

提醒:这里的业务组件并不蕴含 业务 UI 组件

根底组件拆分

根底组件通常依据职责繁多准则进行拆分比拟容易拆分,然而会有一些拆分场景须要思考:

应用插件组件拆分根底组件扩大能力

将外围根底能力和扩大能力拆分到不同的组件。以网络库为例,除了提供最外围的 接口申请 能力,同时可能还蕴含一些扩大能力例如 HTTPDNS 网络性能检测 弱网优化 等能力。但这些扩大能力放在网络库组件外部可能会导致以下问题:

  • 扩大能力会使组件本身代码变的更加简单。
  • 应用方不肯定会应用所有这些扩大能力违反了 最小依赖准则。带来更多的包体积,引入更多的组件依赖,减少模块间的耦合度。
  • 相干的扩大能力不反对灵便的替换 / 插拔。

所以这种场景咱们能够思考依据理论状况将扩大能力拆分到相应的插件组件,应用方须要时再依赖引入对应插件组件。

业务组件拆分

业务页面拆分形式

针对业务页面能够应用 技术栈 业务域 页面粒度 三种形式进行更细粒度的划分,通常至多要拆分到 技术栈 业务域 这一层级,页面粒度 拆分依据具体页面复杂度和复用诉求。

  • 基于技术栈进行拆分 – 不同的技术栈须要拆分到不同的组件进行治理。
  • 基于业务域进行拆分 – 将同一个业务域的所有页面拆分一个组件,防止不同业务域之间造成强耦合依赖关系,同一个业务域通常会有更多复用和通信的场景也不便开发。例如订单详情和订单列表可搁置在一起治理。
  • 基于页面粒度进行拆分  – 单个页面 简单度过高 或须要被 独自复用 时须要拆分到一个单个组件治理。

提醒:搁置在繁多组件内的多个页面之间也应适当升高耦合水平。

第三方库

第三方库应拆分独自组件治理

第三方库应应用独立的组件进行治理,一方面有利于组件复用同时防止多个反复第三方库导致符号抵触,另一方面有利于后续降级保护。

一些提醒

缩小应用通用聚合公共组件

为了防止拆分过多的组件,咱们通常会创立聚合组件将一些 代码量不多 / 性能类似 的类放到同一个组件内,例如Foundation 组件UI 组件。然而很多时候会存在滥用的场景,该当警觉这类公共聚合组件。上面是一些公共聚合组件容易滥用的场景:

  • 增加一个新性能不晓得该当加在哪里时,就加到公共聚合组件内,工夫久了当前公共组件依赖特地多。
  • 公共组件增加了一个非常复杂的能力,导致复杂度变高或者引入大量依赖
  • 太多能力聚合到一起。例如将网络库、图片库这些能力放在同一个组件内
  • 根底 / 业务 UI 组件没有拆分。根底 UI 组件通常只提供最根底的 UI 和十分轻量的逻辑,业务组件通常会充当根底 UI 组件的数据源以及业务逻辑。

然而也不能完全避免应用聚合公共组件,不然会导致产生更多的小组件减少保护老本。然而咱们将一个能力增加到公共聚合组件时能够依据以下几个条件来衡量:

  • 是否会引入大量新的依赖
  • 性能复杂度、代码数量,太简单的不应该增加到公共组件
  • 能力是否须要被独自复用,须要独自复用就不应该增加到公共组件
第三方库思考不间接对外裸露应用

当存在以下状况时可思考对第三方库进行适当的封装防止间接裸露第三方库:

  • 应用方通常只须要应用大量 API,第三方库会对外裸露大量 API 减少应用难度,同时可能导致一些平安问题
  • 对外暗藏具体实现,不便后续更换其余第三方库、自实现、第三方库产生 Break Change 变更时降级更容易
  • 须要封装扩大一些能力让应用方应用起来更容易

以网络库为例:1. 通常须要对接公司外部的 API 网关能力所以须要适当做一些封装,例如签名或者加密策略。2. 应用方通常只须要用到一个通用的申请办法无需对外裸露太多 API。3. 为了平安通常须要对业务方暗藏一些办法防止谬误调用,例如全局 Cookie 批改等能力。4. 对外暗藏具体第三方库能够不便变更。

第三方库尽可能防止间接批改源码

第三方库组件尽可能不要间接批改源码,除修复 Bug/Crash 之外尽可能防止带入其余性能代码导致前面更新艰难。须要增加性能时能够通过在其余组件内应用第三方库对外裸露的 API 进行能力扩大。

组件间依赖关系

业务组件间通信形式抉择

松耦合通信形式比照

基于以上表格中各种计划的优缺点,集体举荐应用 URL Scheme 协定作为 页面路由 通信形式,应用 服务接口 提供业务性能服务。告诉订阅场景可应用 告诉 RxSwift形式提供一对多的订阅能力。

服务接口

服务接口对应的实现和页面是否须要拆分

以购物车服务为例,购物车接口服务提供了增加购物车的能力。加车服务具体的实现应该放在购物车页面组件内还是独立进去搁置在独自的组件。将购物车服务实现和购物车页面拆分的 长处 是购物车服务和购物车页面更好的解耦,都能独自反对复用。毛病 是开发效率升高,批改购物车性能时可能会波及到同时批改 购物车服务 组件和 购物车页面 组件。

所以在须要独自复用 服务 页面 的场景时可思考别离拆分出单个组件(例如购物车服务作为一种通用能力提供给下层跨平台容器能力)。但即便在同一个组件内也倡议对服务和页面应用分层设计的形式进行解耦。

服务接口是否须要拆分

个别我的项目可能至多会有 10+ 个服务接口,这些服务接口应该对立寄存在单个组件还是每个接口对应一个组件。

  • 对立寄存:长处是一起治理更快捷不便。毛病是所有接口对应一个组件版本,不能反对繁多接口应用不同版本,不利于须要 跨 APP复用的我的项目。并且应用方可能会引入大量无用的接口依赖。
  • 离开寄存:长处是每个接口可应用不同的版本并且应用方只须要依赖特定的接口。毛病是会产生更多的组件仓库,组件数量也会减少依赖查找的耗时。
    所以大型项目抉择离开寄存的形式治理接口绝对更适合一点。也能够思考将大部分最外围的服务接口搁置到一起治理。
反对 Swift 的服务接口实现举荐

应用 Swift 实现传统的服务接口模式通常会遇到以下两个问题:

  • 接口须要同时反对 Objective-CSwift调用,同时心愿应用 Swift 个性设计 API。如何实现 Objective-CSwift协定能够 复用 一个实例
  • Swift对于动态性反对比拟弱,纯 Swift 类无奈反对运行时动态创建只能在注册时创立实例

基于以上问题,集体举荐应用上面的形式实现接口服务模式:

  • 应用 Objective-C 协定提供最根底的服务能力,之后创立 Swift 协定扩大提供局部 Swift 个性的 API
  • 接口实现类继承 NSObject 反对运行时动静初始化
// @objc 协定
@objc public protocol JDCartService {func addCart(request: JDAddCartRequest, onSuccess: () -> Void, onFail: () ->) 
}
// swift 协定
public protocol CartService: JDCartService {func addCart() async

    func addCart(onCompletion: Result)

}

// 实现类
class CartServiceImp: NSObject, CartService {// 同时实现 Objc 和 Swift 协定}
服务应该中心化注册还是分布式注册

中心化注册是在 宿主 APP启动时对立注册服务接口的对应实现实例,分布式注册是在组件内组件本身进行注册。集体举荐中心化注册的形式在 宿主 APP启动时对立进行注册治理,明确服务的实现方更清晰,同时防止不同组件蕴含同一个服务接口的不同实例导致的抵触。

组件版本兼容

审慎应用常量、枚举、宏

因为组件编译公布的时候会生成二进制库,编译器会将依赖的 常量、枚举、宏 替换成对应的 值或代码 ,所以当后续这些 常量、枚举、宏 产生变更的时候,已生成的二进制库并不会扭转导致打包的时候仍然应用的旧值,必须从新公布应用这些值的组件才行。所以该当尽量避免批改 常量、枚举、宏 值,如果已知后续可能会变更的状况下应防止应用 常量、枚举、宏

根底组件 API 向后兼容
  • 对外 API 需保障向后兼容,应用 增加 API的形式扩大现有能力,防止对原有 API 进行 break change 改变或移除
  • 应用对象封装传递参数和回调参数,防止对 原有 API进行批改

提醒:特地是对于 Objective-C 这类动静调用的语言来讲,打包构建时并不能发现调用的办法不存在、参数谬误这些问题。所以咱们该当尽可能防止现有办法的变更。同时也举荐更多应用 Swift 编译器能够发现这些问题提醒编译谬误。

缩小公布大版本

Cocoapods 为例,组件公布大版本会导致依赖此组件的所有组件都必须同时降级到大的版本从新公布,这样会给组件应用放带来极大的更新老本。所以组件应该缩小公布大版本,除非必须强制所有组件肯定要降级。

优先选择 接口服务 缩小裸露View 类

当只关注 API 提供的能力并不关注 API 提供的状态时尽可能通过 API 的形式来裸露能力。因为裸露接口办法相比视图 View,调用方只须要依赖接口办法相比依赖 View 类能够更小化的依赖,同时接口对于实现方将来扩大能力更灵便。以抉择用户地址 API 为例,通常调用方并不关注实现方以 弹窗 的形式还是 页面 的形式提供交互能力让用户抉择,只关注用户最终抉择的地址数据。并且调用方不须要解决弹窗和页面的展现逻辑应用起来更不便,也便于实现方之后批改交互方式。

应用接口的办法
addressService.chooseAddress {address in}
应用 View 的形式
let addressView = AddressView()
addressView.callback = { address in
    ///
}
addressView.show()
防止应用 Runtime 反射动静调用类

该当尽量避免应用 反射 机制在运行时应用其余类,因为类的实现方不能保障这个类始终存在,编译器也无奈检测出谬误。某些基于 AOP 的性能可能会应用到这种动静反射能力,然而大部分场景应该尽量避免。

第三方库

第三方库组件不容许依赖其余组件。

第三方库组件不容许依赖其余组件。

品质保障

尽管后面讲到了很多标准和准则,然而并不能保障咱们的这些标准和准则能够强制执行。所以咱们须要在组件公布和利用打包阶段增加一些卡口平安检测,及时发现组件化依赖问题防止带入线上。

CI 查看

组件公布

在组件公布时增加一个安全检查,防止不合乎依赖标准的组件公布胜利。通常咱们能够增加以下依赖查看规定:

  • 第三方库不可依赖其余组件
  • 根底组件不可依赖业务组件
  • 业务组件不可间接依赖业务组件
  • 组件间通常不可相互依赖
  • 不容许组件层级间反向依赖
版本集成标准

集成系统须要将特定需要和组件版本关联到一起,打包时会依据版本需要主动退出对应的组件版本。防止开发同学间接批改组件版本引入不应该退出到版本的个性。

打包构建

在宿主 APP 打包时,提前检测出接口服务存在的问题,防止带入到线上。通常能够检测以下问题:

  • 服务接口对应的实现类不存在
  • 服务接口对应的实现类没有实现所有办法
  • 应用 ObjC Runtime 动静调用类和办法

线上异样上报

线上查看能够帮忙咱们在灰度公布的及时发现问题及时修复,通常能够发现以下问题:

  • 路由跳转对应的页面不存在
  • 接口服务对应的实现类不存在
  • 接口服务对应的办法不存在

可量化指标

咱们能够通过一些指标来量化整个工程组件化的衰弱水平,以下列出常见的一些指标:

根底组件依赖数量

组件依赖的所有根底组件总数,当依赖的根底组件总数过高时应该及时进行重构。如果大量的业务组件都须要依赖十分多的根底组件,那可能阐明根底组件的依赖关系呈现了很大的问题,这时候须要对根底组件进行优化重构:

  • 思考应用接口服务对外裸露能力,组件层级须要晋升
  • 思考将局部能力拆分出为独立的新组件
业务服务依赖数量

业务组件对其余业务服务组件的依赖数量。当业务组件依赖了其余业务服务调用时也会造成隐式的耦合关系,依赖过多时该当思考是否应该对外裸露可监听变动的告诉订阅以 订阅察看 的形式代替被动调用

谬误依赖关系数量

谬误的依赖关系应该及时优化革新。

一些常见的问题

根底组件应该间接裸露还是应用接口对外裸露

根底组件应该间接应用 头文件 API裸露还是应用 接口 间接裸露有时候很难衡量,然而能够依据一些个性来衡量抉择:

API 间接裸露
  • 性能繁多 / 依赖少 – 一些工具类,例如Foundation
  • API 简单  – API 十分多如果应用接口须要形象太多接口,例如 网络库 日志
  • UI 组件  – 须要间接裸露UIViewUI 组件,例如UIKit
接口对外裸露
  • 可扩展性 – 基于接口能够灵便替换不同的实现,例如定位能力能够应用零碎自带的 API,也能够应用不同地图厂商的 API
  • 缩小依赖引入 – 升高应用方的接入老本,进步日常开发 / 组件公布效率
  • 可插拔能力  – 对应的能力可移除,同时也不影响外围业务
    提醒:这些以接口对外裸露的 API 还有一个劣势是能够形象成容器化的 API,造成对立的标准规范。应用方调用同样的 API,不同的 APP 能够提供不一样的实现。

小我的项目是否应该做组件化

集体认为小我的项目也能够做组件化,须要关注的是须要做到什么水平的组件化。通常来讲 越大型越简单 的我的项目组件化拆分的 粒度更细组件数越多 。对于小我的项目来讲尽管晚期做组件化的收益并不大,也须要适当思考将来的发展趋势预留肯定的空间,同时也须要适当思考模块间的依赖关系防止前期拆分模块时很艰难。刚开始做粒度比拟粗的组件化,之后在我的项目倒退中一直的调整组件化的粒度。也能够思考应用相似Monorepo 的形式来治理我的项目,代码都在一个仓库中治理,通过文件夹隔离标准模块间的依赖。

繁多工程如何革新为组件化工程

一般来讲咱们须要应用循序渐进逐渐重构的策略对原有我的项目进行革新,然而有一些模块能够优先被组件化拆分升高整个组件化的难度:

  • 优先拆分出最外围的所有业务模块可能都须要应用的组件,这些组件拆分实现当前能力为之后业务模块拆分提供根底。例如 FoundationUI 组件 网络库 图片库 埋点日志 等最根底的组件。
  • 优先拆分 不被其余组件依赖 被其余组件依赖较少 的模块组件,这些模块绝对比拟独立拆分起来比拟高效并且对现有工程革新较小。例如 性能监控 微信 SDK这类绝对独立的能力。

组件化带来的额定老本

组件化架构可能会带来以下这些额定的老本:

  • 治理更多的组件 git 仓库
  • 每次组件公布都须要从新编译 / 公布
  • 因为组件应用方都是应用相应的组件二进制库,所以调试源码会变的更艰难
  • 开发组件治理平台,治理组件版本、版本配置表等能力
  • 每个组件须要有本人的 Example 工程进行日常开发调试
  • 解决可能存在的组件版本不统一导致的依赖抵触、编译谬误等问题
  • 需要可能会波及到多组件改变,如何进行 Code Review 版本合入查看

Monorepo

我集体并没有在理论的我的项目中应用过 Monorepo 形式治理我的项目。Monorepo是将所有组件代码放在单个 git 仓库内治理,而后应用文件夹拆分为不同的组件。不同文件夹中的代码不能间接依赖应用,须要配置本地文件夹的组件依赖关系,在实现组件化的同时防止拆分太多的 git 仓库。不过集体认为 Monorepo 同时也须要解决以下几个问题:

  • 编译耗时优化 – 将所有源码放在单个工程中会导致编译变慢,所以必须优化现有工程编译流程,升高非必要的反复编译耗时。
  • 组件版本治理  – 在组件化工程中咱们能够通过配置组件的特定版本来治理性能是否合入到版本中,但在Monorepo 中只能通过分支 Merge Request 来治理个性是否合入,回滚也会更加繁琐。
  • 高质量 CI 流程  – 在单个仓库中,当一个开发者有仓库权限时他就能够批改该仓库的任意代码。所以必须欠缺代码合入标准,更高标准的Code Review 集成测试查看 自动化查看 防止问题代码带到线上。

总结

集体认为并不存在一个完满的架构,咱们本身的组织架构、业务、人员都在变动,架构也须要随着这个过程进行适当的调整和重构,最重要的是咱们能及时发现架构中存在的问题并且有志愿 / 能力去调整防止始终沉积变成更大的技术债权。

同时工程架构的扭转也会肯定水平的扭转开发人员的分工,对于大型工程来讲组件化的水平更高,每个开发人员的工作分工会更细。对于底层根底组件的开发,须要提供更多高性能 / 高质量的根底组件让下层业务开发人员更加效率的撑持业务,技术深度也会更加深刻。对于下层业务开发,更多是应用这些底层根底组件,同时可能也须要把握多种跨端 UI 技术栈疾速撑持业务,技术栈会更广然而不会太深刻。

作者:京东批发 何骁

起源:京东云开发者社区

正文完
 0