开源了一个支持渐进式组件化的框架(CC) - V2EX
luckybilly

开源了一个支持渐进式组件化的框架(CC)

  •  1
     
  •   luckybilly Jun 17, 2018 13286 views
    This topic created in 2892 days ago, the information mentioned may be changed or developed.

    前言

    项目大了,编译慢了,开发效率低了,怎么办? 也许你已经知道了组件化,但项目迭代任务紧张,根本没有时间进行整体解耦,更害怕一下子改动太大导致的风险不可控,不敢大改,怎么办?

    先别急着放弃,渐进式组件化了解一下

    背景故事

    在实行组件化改造之前,我们对业内的一些技术文章及开源库进行调研之后,发现基本上千篇一律地都是基于路由这种方案作为通信引擎来实现组件化,重要的是组件化之前得先解耦原来的项目代码。很尴尬,我们没那么人力和时间来一下子做这么一大块事情。这时候我们是这样想的:

    • 可不可以先不解耦?

       这个问题问得好,可是...不先解耦,组件怎么单独以 app 运行呢? 
    • 新业务新 module 可以无耦合,总可以单独运行调试了吧?

       组件需要登录怎么办? 需要跟其它组件通信怎么办? 
    • 用 URLScheme 来跨 app 通信总可以了吧?

       页面跳转还行,虽然有个中转页面,仅在开发期间使用勉强也能接受 但如何获取服务?那可是要查找接口的实现类,跨 app 调用时行吗? 
    • 将需要调用的业务代码一起打包总可以了吧?

       确实可以,但这些代码在哪里?主 app module 看一下? 难不成要跟主 app 一起打包?没解耦的那些 module 要全部编译才行哦 
    • 那...有没有这样一种方案:让我们可以立即就在新的业务上用组件化的形式进行开发(单组件 module 以 apk 的形式编译运行调试,并且可以与其它业务模块互相通信),享受到组件化编译速度快、调试效率高的好处,又不需要马上解耦项目,而是在迭代过程中利用偶尔出现的碎片空闲时间来一个一个慢慢地解耦呢?

       好想法,这就是我们想要的:立即组件化开发 & 渐进式组件化改造 不过很遗憾,好像没有什么现成的方案可用。 那我们就自己设计一个吧! 

    正式由于项目代码耦合度高,解耦困难,而且迭代任务紧张,没办法单独抽出时间来解耦,导致组件化改造一直停留在口头上。

    为了在影响业务迭代业务开发的前提下也能用组件化的形式来进行开发,我们设计了一个支持立即组件化开发 & 渐进式组件化改造的框架:CC (已开源,点这里看源码

    快速了解 CC

    • CC : Component Caller
    • 是一套基于组件总线的组件化实施方案
    • 一静一动,开发时运行 2 个 app:
      • 静:主 App (通过跨 App 的方式单组件 App 内的组件)
      • 动:单组件 App (通过跨 App 的方式调用主 App 内的组件)
    • 支持渐进式组件化改造
      • 解耦只是过程,而不是前提

    用 CC 来立即开始组件化开发并渐进式地改造你的项目

    先了解一下渐进式组件化改造的概念

    以 i 百联为例(上海百联集团旗下的一个电商 App )来看一下组件化之前项目中存在的耦合情况:

    • 先是一个启动页(Splash),里面会有一些初始化、加载广告等业务,然后跳转到主页,广告可能跳转到商品详情页、用 web 模块打开 H5 活动页,存在不少耦合
    • 主页中有:首页、分类、发现、购物车、我的等 Fragment 页面 /业务模块,还要处理 push 等功能,也会存在不少耦合
    • 首页中需要跳转到商品搜索,还有各种业务 Fragment/业务模块,需要跳转商品详情、各种列表页、各种活动 H5 页面,耦合程度非常高
    • 很多需要用户登录的功能模块都需要耦合登录模块
    • 商品详情页、Web 模块等模块几乎所有的业务模块都会用到,耦合程度不言而喻
    • 百联到家、奥莱代购、分享、埋点、收银台、LBS、秒杀等等等等

    最终项目的耦合状态是这个样子的:

    组件化之前项目的耦合状态

    为了能让大家看得清楚,图片上仅仅列出了有限的几个模块,但即使是这样,我们用一团乱麻来形容它也毫不为过。

    我们的工程师一直是在这样的环境下进行业务迭代开发和 bug 修复,改代码要小心翼翼,生怕引起其它逻辑出 bug,最主要的是开发 /调试效率非常低:在使用了 maven 的情况下,在 15 高配的 macbook pro 上编译运行每次都要花 3-5 分钟,在 16GB 内存的 windows 台式机上更是动辄 10-15 分钟,严重影响开发效率,组件化势在必行。

    但业务迭代排期时间非常紧张,测试资源不足,我们需要的是:立即组件化开发 & 渐进式组件化改造

    在了解渐进式组件化改造之前,先来看看与之相对的非渐进式组件化改造

    非渐进式组件化

    非渐进式的组件化方案要求我们必须在组件化初期立即对项目进行解耦,至少是我们需要被新业务调用到的组件需要解耦成组件,否则新业务组件脱离主 app 单独运行调试的时候无法正常工作。

    在渐进式组件化的方案中,可以先不用解耦,只需要让单独运行的组件能够调用到主 App 中的功能即可。思路是这样的:

    • 新业务以组件形式开发
    • 新组件需要调用的主 App 中的业务,在对应的模块中创建一个组件类,对外暴露对应的服务,供其它组件调用,并不需要现在就将这个模块解耦
    • 新组件通过跨 App 的方式调用主 App 中的组件
    • 主 App 也可以通过跨 App 的方式调用到单独运行的组件 App 中的组件
    • 在同一个 module 中可以创建多个组件类,将来解耦时将对应的组件类移动到解耦后的 module 中即可

    渐进式组件化

    动画旁白:

    • 接入 CC 后,我们的项目就已经组件化完成了
    • 新业务:会员积分,以组件的形式进行开发:创建一个IComponent接口的实现类,对外暴露自身提供的服务,并且可以独立编译运行调试
    • 会员积分组件需要用到登录、订单和 Web 模块的功能
    • 在登录、订单、Web 模块对应的 module 或包下创建对应的IComponent接口实现类,对外暴露自身的服务,让会员积分组件能够跨 App 调用到这些组件
    • 此时如果我们有时间,可以进行解耦,将相对比较容易解耦的登录模块解耦出来,改造成一个组件
    • 这时候又来了新业务:用户浏览记录,仍然以组件的方式开发
    • 浏览记录组件需要能打开主 App 的商品详情页,在商品详情页对应的 module 或包下创建对应的IComponent接口实现类,对外暴露自身的服务,让浏览记录组件能够跨 App 调用到商品详情组件
    • 其它的业务也可以用同样的方式来做
    • 利用每个迭代过程中的出现的一些碎片空闲时间,将原来项目中的业务模块逐个从主 App 中解耦出去变成组件
    • 如果遇到有些几个组件暂时无法完全解耦,但如果作为一个整体可以解耦出来,就像动画中示例的商品详情和订单模块一样,可以将它们作为一个整体解耦出来(一个 module,多个IComponent对应多个组件),等将来有时间了再继续拆分
    • 持续地按照这个思路进行解耦,最终目标是将整个项目中的所有业务模块都解耦出来变成组件

    CC 是怎么做到这一点的?

    首先,CC 的核心通信引擎采用的不是路由方案,而是组件总线方案(路由 vs 组件总线

    CC 采有 2 套调用流程:App 内部组件调用和跨 App 调用。

    框架在接收到组件调用请求时,优先查看当前 App 内是否有本次 CC 调用指定的组件

    • 有: 执行 App 内部组件调用流程
    • 没有:检查当前 app 是否开启跨 app 调用功能:
      • 未开启: 返回-5 的状态码,代表未找到指定的组件
      • 已开启: 执行跨 App 组件调用流程

    App 内部调用执行流程

    采用组件总线的方案,在 App 内部调用组件时,等效于直接调用IComponent.onCall(cc)方法,将调用方设置的调用参数传递给组件,组件执行完之后将执行结果返回给调用方,这个过程中没有使用反射,执行效率高

    在这个过程中,CC 完成了一次调用请求的转发:查找到组件对象,并将调用其 onCall 方法,将调用参数发送给它,并将组件执行的结果返回给调用方

    悄悄地告诉你:CC 中自带 3 种 AOP 策略,例如动画中显示的CustomerInterceptors就是其中之二:全局拦截器和针对本次 CC 调用的拦截器。定义一个拦截器也很简单:实现IGlobalCCInterceptor接口即可

    跨 App 调用执行流程

    通过RemoteCCInterceptor与另一个 App 的ComponentService建立连接,将 CC 中的调用参数传递给ComponentService,在ComponentService中使用这些参数,发起一个 App 内部的 CC 调用,最终通过LocalCCInterceptor调用到IComponent.onCall(cc)

    组件的执行结果原路返回给调用方

    在这个过程中,CC 完成了 2 次调用请求转发:

    • 跨 App 转发:将调用参数转发给另一个 App
    • App 内部转发:找到组件对象,调用其 onCall 方法将调用参数发送给它

    可以看出,跨 App 调用时:

    • 调用组件的代码与在 App 内部调用时完全一样,无需任何改动
    • 实现组件的代码IComponent.onCall(cc)方法也是在LocalCCInterceptor中调用,与在 App 内部调用时一样

    总结

    本文从我们在实际实施组件化过程中的一些思考入手,引入了渐进式组件化的概念,介绍了用 CC 来实现立即组件化开发 & 渐进式组件化改造的实施步骤。

    并用动画的方式向大家展示了渐进式组件化与非渐进式组件化的区别以及支撑 CC 实现渐进式组件化的组件调用流程。

    本文中的图片及动画取自上周末(6 月 9 日)在爱奇艺移动技术沙龙分享时的演讲稿,PPT 文档可在 CC 交流群(QQ 群:686844583)的群文件中下载,欢迎加群交流。

    点击这里了解更多 CC 相关的内容

    No Comments Yet
    About     Help     Advertise     Blog     API     FAQ     Solana     3518 Online   Highest 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 71ms UTC 04:27 PVG 12:27 LAX 21:27 JFK 00:27
    Do have faith in what you're doing.
    ubao msn snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86