网易考拉组件化开发经验分享
随着工程日益庞大带来的诸多问题,项目工程化拆分越来越迫切。本文将分享网易考拉在组件化方面的实践经验,重点就组件化拆分原则、路由与服务、工程与协作等问题进行探讨。
组件化整体方案与拆分原则
随着项目越来越大,工程的代码库也越来越庞大,复杂程度带来了一系列的问题。虽然Java提供了package和public protect访问控制工具,但是仍然无法解决代码相互依赖,关联耦合的问题。那么如何明确代码边界,提高不同业务模块之间的协同效率呢?
1.jpg
网易考拉选择安卓官方提供的AAR的方式来划分边界,先将整个工程划分成业务组件和技术组件两大类。其中业务组件包括了考拉的各项业务,比如像首页、社区、导购、搜索等,业务之间相互平行独立,没有依赖关系。技术组件主要是抽象出来的各种技术功能,比如网络库,安全库,图片库等。
然后在业务组件和技术组件的基础上,增加了壳工程和考拉基础库两层。其中壳工程层负责将业务组件组合打包。考拉基础库则主要对下方的技术组件进行整合,提供统一的API,简化依赖关系;同时它还会将一些暂时无法抽离的一些公用功能进行整合,如网络逻辑、测试逻辑等。
2.jpg
整体拆分按的原则是保证各组件水平方向互不依赖,垂直方向只有从上而下的依赖关系。
路由与服务
当组件化框架搭好之后,分离的组件之间需要通讯,即需要路由和服务来支持。通讯发生的场景主要是业务组件之间的,因为技术组件主要是技术功能的抽象,一般情况下技术组件之间不需要通信,在少数需要通信的场景可以通过接口来实现。
业务组件之间的通信主要有两类:无返回值的调用和有返回值的调用。无返回值的调用通常都是页面跳转,用路由来实现;有返回值的调用通常都是业务之间的逻辑调用,比如购物车调用SKU、社区红人模块调用红包等,一般用服务来实现。
路由功能
路由功能在安卓页面中以Activity承载,由于形式比较单一,使用java标签的方式可以很好的实现。它要求功能在应用内有全局卫唯一的页面名即可。路由需要支持传递参数,Activity和Fragment取用方便,我们可以通过intent来实现。在H5混合开发的情况下,我们需要增加域名和路径的配置。如果需要通过外链启动APP,可以通过配置落地页来实现。最后我们需要在路由上增加拦截的功能逻辑,比如登录判断、系统权限申请等。
路由生成与加载使用
不同的业务组件编码器在落地页打上自己的路由标签,配置路径,将host或者pagename配置上去,然后再编译期通过apt生成业务的路由表。业务路由表在transfer会通过静态扫描收集整合成全局路由表,作为运行时页面跳转的指引。
3.jpg服务功能
服务功能的关键在于隔离声明和实现。因为服务的实现是由具体业务实现的,比如添加购物车的服务,它可能是在某个具体的模块实现,但是它的接口声明是需要全局可见,每个业务都可以看到它的声明并进行调用。服务的静态初始化也是在transfer阶段进行完成的。
此外,网易考拉在实践中发现某些服务(比如账号服务)需要查看APP的生命周期,所以网易考拉在服务中增加了生命周期接口与优先级逻辑。
服务的生成与加载使用
以社区业务为例,在业务API层有一个Seeding Service,它可以打开文章或者拉取社区的用户状态。这里只有一个服务,实际业务中每个业务可以包含多个服务。当社区业务想调用加购物车的动作时,它可以引用交易单独打包声明的价包,然后通过下方的KLbase库获取addcart()接口,把通讯关联起来。
4.jpg
考拉拆分过程举例
下面以社区业务为例分析组件化拆分的过程。
社区业务独立
业务独立首先要确定好业务的边界,包含哪些落地页面、功能以及动态页面等。其次在组件下新建子Module seeding,放到APP的dependence中,调整依赖关系,使APP依赖于这个子module。此时将社区代码移入会发现社区对APP中的兄弟业务的调用飘红,需要根据依赖关系进行解决。在这之后将APP中对社区的依赖服务注释掉,会发现各业务对社区的调用飘红,这里我们主要是需要使用路由和服务去解决各模块之间的通信问题。最后社区工程代码独立以后,将它单独打包,分为接口和声明两个包。
考拉工程整体节奏
整体上,整个工程拆分的节奏与社区拆分类似。先将产品需求、开发和运营阶段有明显边界的业务拆分出来,相当于进行一个解耦的工作。在解耦的过程中,一些共用的模块也会继续拆分,如account业务、share功能等。大部分业务拆分完成后,剩余的独立业务根据人力分配或者单独打包。最后主工程就变成了一个壳,仅包含打包组合的逻辑,没有业务逻辑。
工程组合与协作方式
下方是组件化拆分之后的开发流程图。
业务模块组件化以后,每个业务都是以带版本号的AAR包的形式来做的。当开发基线确定后,确定要变动的范围,拉出分支,然后开始开发。在各业务研发阶段,将价包替换成业务源码,并生成下一个版本号(图中为2.0.0-snapshot动态版本),然后参加下一个阶段的功能冒烟测试。等测试完成后,会将动态版本号编程静态版本号,进行整体的回归工作,在这个阶段可能会有一些小版本的功能迭代,进行bug修复等工作。最后完成之后进行上线,这是版本号与开始已经发生了变更。上线之后,它就变成了下个版本的基线。
5.jpg
组件化集成与单工程的两处区别
单工程开发和组件化开发有两个区别:第一,如何在业务价包和源码之间进行切换;第二,如何简化构建上传流程。
1、如何在业务价包和源码之间进行切换?
我们在主架中声明一个版本号的文件, 下面有四个业务,compat、customer、帐号和社区,对应四个版本号。
6.jpg
在这个括号中,定义一个返回string的函数。然后在settings grade(这是一个包含引入module的文件)as_sources的map中,如果我们要复写账号模块,就需要在这里配置它的相对路径和绝对路径。
7.jpg
配置完成后具体的替换会发生在下方这个函数中,它会引入刚才定义的account,修改它的projectDir,变成一个本地的路径,重新把这个路径定义成一个返回project的函数,放回到刚才的as_sources中。
8.jpg
这时在APP中进行具体依赖时,它会根据刚才的函数返回依赖结果:如果刚才函数进行了重写,它就会返回一个价包依赖;如果没有重写就会返回一个本地sources依赖。
为了让代码快速打出一个新版本,我们将changingmodule和dynamicVersion两个参数配置成了0。
9.jpg
2、如何简化构建上传流程?
打包是一个繁琐的过程,如何简化上传流程?我们做了一个自己的插件,进行了封装,它提供配置应用打包的几个参数(artifactID、version和groupID),配合下方两个命令可以发布对应的版本。
10.jpg 11.jpg后期工作展望
在组件化完成以后,后面要做的工作主要有四点。
第一,组件管理。随着各业务都实现组件化后,各个版本号的发布将进行协作化的管理。
第二,组件复用。包含技术组件和业务组件,通过复用实现快速拼装出一个新的应用。
第三,CI集成。网络考拉期望组件化以后,把线上的日志归类到某一个组件上去,希望把开发到上线运营的逻辑,通过数据的信息回馈给业务。
最后一个就是集成到脚手架。我们希望新业务的开发方式都能够脚手架的方式进行开发。