前言

关于中大型移动端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架构的一些特色,多组件、多技能栈、事务间需求解耦、复用底层根底组件。依据这个事例来介绍上面的三点准则。 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<Data, Error>)
}
// 完成类
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技能栈快速支撑事务,技能栈会更广可是不会太深化。