关于app开发:移动端APP组件化架构实践-京东云技术团队
前言对于中大型挪动端APP开发来讲,组件化是一种罕用的我的项目架构形式。集体最近几年在工作我的项目中也始终应用组件化的形式来开发,在这过程中也积攒了一些教训和思考。次要是来自在日常开发中应用组件化开发遇到的问题以及和其余开发同学的交换探讨。 本文通过以下问题来介绍组件化这种开发架构的思维和常见的一些问题: 为什么须要组件化组件化过程中会遇到的挑战和抉择如何保护一个高质量的组件化我的项目提醒:本文说的组件化工程是指Multirepo应用独立的git仓库来治理组件。组件化能够带来什么繁多工程架构遇到的问题在组件化架构之前,传统应用的工程架构次要是以Monolithic形式的繁多工程架构,也就是将所有代码放在单个代码仓库里治理。繁多工程架构应用了这么多年为什么忽然遇到了问题,这也引入了APP我的项目开发的一个大背景,现有中大型APP我的项目变的越来越简单: 多APP我的项目并存 - 团体外部存在多个APP我的项目,不同APP心愿能够复用现有组件能力疾速搭建出新的APP。性能增多 - 随着我的项目性能越来越多,代码量增多。同时须要更多的开发人员参加到我的项目中,这会减少开发团队之间合作的老本。多语言/多技术栈 - 引入了更多的新技术,例如应用一种以上的跨平台UI技术用于疾速交付业务,不同的编程语言、音视频、跨平台框架,减少了整个工程的复杂度。以上这些业务倒退的诉求就给传统繁多工程架构形式带来了很多新的技术要求: 工程效率工程代码量过大会导致编译速度迟缓。单git工程提交同时可能带来更多的git提交抵触和编译谬误。品质问题如何将git提交关联到对应的功能模块需要。发版时进行合规查看防止带入不标准的代码,对整个功能模块回滚的诉求。如何在单仓库中管控这么多开发人员的代码权限,尽可能防止不平安的提交并且限度改变范畴。更大范畴的组件复用根底组件从反对单个APP复用到反对多个APP复用。不只是根底能力组件,对于业务能力组件也须要反对复用。(例如一个页面组件同时在多个APP应用)跨平台容器须要复用底层组件能力防止反复开发,同时不同跨平台容器API须要尽量放弃对立,底层基础设施向容器化倒退反对业务跨APP复用。跨技术栈通信因为页面导航多技术栈混合共存,页面路由须要反对跨技术栈。跨组件通信须要反对跨语言/跨技术栈通信。更好的解耦页面解耦。因为页面导航栈混合共存,页面本身不再清晰的晓得上游和上游页面由什么技术栈搭建,所以页面路由须要做到齐全解耦隔离技术栈的具体实现。业务组件间维持松耦合关系,能够灵便增加/移除,基于现有组件能力疾速搭建出不同的APP。对于同一个服务或页面能够插件化形式灵便提供多种不同的实现,不同的APP宿主也能够提供不同的实现并且提供A/B能力。因为包体积限度和不同组件蕴含雷同符号导致的符号抵触问题,在复用组件的时候须要尽可能引入最小依赖准则升高接入老本。组件化架构的劣势基于以上这些问题,当初的组件化架构心愿能够解决这些问题晋升整个交付效率和交付品质。 组件化架构通常具备以下长处: 代码复用 - 性能封装成组件更容易复用到不同的我的项目中,间接复用能够进步开发效率。并且每个组件职责繁多应用时会带入最小的依赖。升高了解复杂度 - 工程拆分为小组件当前,对于组件应用方咱们只须要通过组件对外裸露的公开API去应用组件的性能,不须要了解它外部的具体实现。这样能够帮忙咱们更容易了解整个大的我的项目工程。更好的解耦 - 在传统繁多工程项目中,尽管咱们能够应用设计模式或者编码标准来束缚模块间的依赖关系,然而因为都寄存在繁多工程目录中短少清晰的模块边界仍然无奈防止不衰弱的依赖关系。组件化当前能够明确定义须要对外裸露的能力,对于模块间的依赖关系咱们能够进行强束缚限度依赖,更好的做到解耦。对一个模块的增加和移除都会更容易,并且模块间的依赖关系更加清晰。隔离技术栈 - 不同的组件能够应用不同的编程语言/技术栈,并且不必放心会影响到其余组件或主工程。例如在不同的组件内能够自由选择应用Kotlin或Swift,能够应用不同的跨平台框架,只须要通过标准的形式暴露出页面路由或者服务办法即可。独立开发/保护/公布 - 大型项目通常有很多团队。在传统繁多我的项目集成打包时可能会遇到代码提交/分支合并的抵触问题。组件化当前每个团队负责本人的组件,组件能够独立开发/保护/公布晋升开发效率。进步编译/构建速度 - 因为组件会提前编译公布成二进制库进行依赖应用,相比编译全副源代码能够节俭大量的编译耗时。同时在日常组件开发时只须要编译大量依赖组件,相比繁多工程能够缩小大量的编译耗时和编译谬误。管控代码权限 - 通过组件化将代码拆分到不同组件git仓库中,咱们能够更好的管控代码权限和限度代码变更范畴。治理版本变更 - 咱们通常会应用CocoaPods/Gradle这类依赖管理工具来治理我的项目中所有的组件依赖。因为每一个组件都有一个明确的版本,这样咱们能够通过比照APP不同版本打包时的组件依赖表很清晰的辨认组件版本个性的变更,防止带入不合规的组件版本个性。并且在呈现问题时也很不便通过配置表进行回滚撤回。提醒:组件化架构是为了解决繁多工程架构开发中的问题。如果你的我的项目中也会遇到这些痛点,那可能就须要做组件化。组件化遇到的挑战尽管组件化架构能够带来这么多收益,但不是只有应用组件化架构就能够解决所有问题。通常来讲当咱们应用一种新的技术计划解决现有问题的时候也会带来一些新的问题,组件化架构能带来多少收益次要取决于整个工程组件化的品质。那在组件化架构中咱们如何去评估我的项目工程的组件化架构品质,咱们须要关注哪些问题。对于软件架构来讲,最重要的就是治理组件实体以及组件间的关系。所以对于组件化架构来讲次要是关注以下三个问题: 如何划分组件的粒度、组件职责边界在哪里?组件间的依赖关系应该如何治理?组件间应该应用哪种形式调用和通信?1. 组件拆分的粒度、组件职责边界在哪里?某种程度上组件拆分粒度也是一种均衡的艺术,咱们须要在效率和品质之间找到一种绝对的均衡。组件拆分粒度太粗:导致组件间耦合严密,并不能利用更好的复用/解耦/进步编译速度这些劣势。组件拆分粒度太细:导致须要保护更多的组件代码仓库、性能变更可能波及多个组件代码的批改/公布,这些都会带来额定的老本,同时组件过多也会导致组件依赖查找过程变的更简单更慢。 组件的职责也会影响咱们对于组件的拆分形式:每个组件的定位是什么,应该蕴含什么样的性能,是否能够被复用,增加某个性能的时候应该创立新组件还是增加到现有组件,当组件简单到肯定水平时是否须要拆分出新个组件。 在拆分组件前须要提前去思考这些问题。 2. 组件间的依赖关系应该如何治理?组件间的依赖形式次要分为间接强耦合依赖和间接松耦合依赖。强耦合依赖是对依赖的组件间接应用对应的API进行调用,这种调用形式长处是简略间接性能更好,毛病是一种齐全耦合的调用形式。(根底组件通常应用这种形式)。松耦合依赖次要是通过告诉、URL Scheme、ObjC Runtime、服务接口、事件队列等通信形式进行间接依赖调用。尽管性能绝对差一点,但这是一种绝对耦合水平比拟低并且灵便的依赖形式。(业务组件通常应用这种形式) 组件间的依赖关系很重要是因为在长期的我的项目开发演化过程中很容易造成一种简单的网状依赖关系。尽管看似应用组件化的形式将模块拆分成不同的组件,然而组件间可能存在很多互相穿插的依赖耦合关系,很多组件都被其余组件间接依赖或隐式间接依赖。这样咱们就背离了组件化架构更好的解耦、更好的复用、更疾速的开发/编译/公布的初衷。 所以咱们须要制订一套标准去束缚和标准组件间的依赖关系:两个组件之间是否能够依赖,组件间依赖方向,抉择强耦合依赖还是松耦合依赖。 3. 组件间松耦合依赖关系应该应用哪种形式调用和通信?松耦合依赖通常能够应用告诉、URL Scheme、ObjC Runtime、服务接口、事件队列等形式通信进行间接调用,然而应用哪种形式更好业界也有很多争执,并且每种形式都有一些优缺点。通常在我的项目中会依据不同的应用场景至多会抉择2种通信形式。 耦合水平低的形式例如URL Scheme,能够做到齐全解耦绝对比拟灵便。然而无奈利用编译时查看、无奈传递简单对象、调用方/被调用方都须要对参数做大量的正确性检查和对齐。同时可能无奈检测对应的调用办法是否存在。 耦合水平高的形式例如服务接口,须要对服务接口办法进行强依赖,然而能够利用编译时查看、传递简单对象、并且能够更好的反对Swift个性。 咱们须要在解耦水平、容易应用、平安上找到一种适合的形式。 提醒:这里的耦合水平高是绝对于耦合水平低的形式进行比拟,相比间接依赖对应组件仍然是一种耦合水平低的依赖关系。组件化架构实际标准和准则基于以上这些组件化架构的问题,须要一些组件化架构相干的标准和准则帮忙咱们做好组件化架构,前面次要会围绕以下三点进行介绍: 组件拆分准则 - 拆分思维和最佳实际领导组件拆分组件间依赖 - 优化组件间依赖关系跨组件调用/通信形式的抉择品质保障 - 防止在继续的工程演化过程中工程质量逐步劣化。次要蕴含平安卡口和CI查看工程实例接下来以一个典型的电商APP架构案例来介绍一个组件化工程。这个案例架构具备之前所说现有中大型APP架构的一些特点,多组件、多技术栈、业务间须要解耦、复用底层根底组件。基于这个案例来介绍下面的三点准则。 组件拆分准则 组件拆分最重要是帮咱们梳理出组件职责以及组件职责的边界。组件划分也会应用很多通用的设计准则和架构思维。 应用分层思维拆分通常咱们能够首先应用分层架构的思维将所有组件纵向拆分为多层组件,下面层级的组件只能依赖上面层级的组件。个别至多能够划分为四层组件: 根底层 - 提供外围的与下层业务无关的根底能力。能够被下层组件间接依赖应用。业务公共层 - 次要蕴含页面路由、公共UI组件、跨组件通信以及服务接口,可被下层组件间接依赖应用。业务实现层 - 业务外围实现层,蕴含原生页面、跨平台容器、业务服务实现。组件间不能间接依赖,只能通过调用页面路由或跨组件通信组件进行应用。APP宿主层 - 次要蕴含APP主工程、启动流程、页面路由注册、服务注册、SDK参数初始化等组件,用于构建打包生成相应的APP。划分层级能够很好的领导咱们进行组件拆分。在拆分组件时咱们须要先辨认它应该在哪一层,它应该以哪种调用形式被其余组件应用,新增加的性能是否会产生反向依赖,帮忙咱们标准组件间的依赖关系。同时按层级拆分组件也有利于底层根底组件的复用。 以下场景应用分层思维就很容易辨认: 根底组件依赖业务组件例子:APP内业务发动网络申请通常须要携带公共参数/Cookie。 没有组件分层束缚 - 网络库可能会依赖登录服务获取用户信息、依赖定位服务获取经纬度,引入大量的依赖变成业务组件。有组件分层束缚 - 网络库作为一个根底组件,它不须要关注下层业务须要携带哪些公共业务参数,同时登录/定位服务组件在网络库下层不能被反向依赖。这时候会思考独自创立一个公共参数治理类,在APP运行时监听各种状态的变更并调用网络库更新公共参数/Cookie。业务组件间依赖方向是否正确登录状态切换常常会波及到很多业务逻辑的触发,例如清空本地用户缓存、地址缓存、清空购物车数据、UI状态变更。 没有组件分层束缚 - 可能会在登录服务内当登录状态切换时调用多个业务逻辑的触发,导致登录服务引入多个业务组件依赖。有组件分层束缚 - 登录组件只须要在登录状态切换时发出通知,无需晓得登录状态切换会影响哪些业务。业务逻辑应该监听登录状态的变更。辨认根底组件还是业务组件尽管很多场景下咱们很容易能辨认解决进去一个性能应该归属于根底组件还是业务组件,例如一个UI控件是根底组件还是业务组件。然而很多时候边界又十分的含糊,例如一个增加购物车按键应该是一个根底组件还是业务组件呢。 根底组件 - 如果不须要依赖业务公共层那该当划分为一个根底组件。业务组件 - 依赖了业务公共层或者网络库,那就应该划分为一个业务组件。分层思维能够很好的帮忙咱们治理组件间的依赖关系,并且明确每个组件的职责边界。 根底/业务组件拆分准则划分根底/业务组件次要是为了强制束缚组件间的依赖关系。以下面的组件分层架构为例: 根底组件 - 根底组件可被间接依赖应用,应用方调用根底组件对外裸露API间接应用。根底层、业务公共层都为根底组件。业务组件 - 业务组件不可被间接依赖应用,只能通过间接通信形式进行应用。APP宿主层和业务实现层都为业务组件。提醒:这里的业务组件并不蕴含业务UI组件。根底组件拆分根底组件通常依据职责繁多准则进行拆分比拟容易拆分,然而会有一些拆分场景须要思考: 应用插件组件拆分根底组件扩大能力将外围根底能力和扩大能力拆分到不同的组件。以网络库为例,除了提供最外围的接口申请能力,同时可能还蕴含一些扩大能力例如HTTPDNS、网络性能检测、弱网优化等能力。但这些扩大能力放在网络库组件外部可能会导致以下问题: 扩大能力会使组件本身代码变的更加简单。应用方不肯定会应用所有这些扩大能力违反了最小依赖准则。带来更多的包体积,引入更多的组件依赖,减少模块间的耦合度。相干的扩大能力不反对灵便的替换/插拔。所以这种场景咱们能够思考依据理论状况将扩大能力拆分到相应的插件组件,应用方须要时再依赖引入对应插件组件。 业务组件拆分业务页面拆分形式针对业务页面能够应用技术栈、业务域、页面粒度三种形式进行更细粒度的划分,通常至多要拆分到技术栈、业务域这一层级,页面粒度拆分依据具体页面复杂度和复用诉求。 基于技术栈进行拆分 - 不同的技术栈须要拆分到不同的组件进行治理。基于业务域进行拆分 - 将同一个业务域的所有页面拆分一个组件,防止不同业务域之间造成强耦合依赖关系,同一个业务域通常会有更多复用和通信的场景也不便开发。例如订单详情和订单列表可搁置在一起治理。基于页面粒度进行拆分 - 单个页面简单度过高或须要被独自复用时须要拆分到一个单个组件治理。提醒:搁置在繁多组件内的多个页面之间也应适当升高耦合水平。第三方库第三方库应拆分独自组件治理第三方库应应用独立的组件进行治理,一方面有利于组件复用同时防止多个反复第三方库导致符号抵触,另一方面有利于后续降级保护。 ...