美团外卖Android平台化架构演进实践

架构大致如图:

image

这是一种灵活、优雅的设计,能够实现部分代码的复用,并能解决两端基础库和UI等差异。这个方案在首页和二级频道页的部分模块使用了一段时间,但是因为学习成本较高等原因推广比较缓慢。另外,这个时期平台化已被提上日程,业务痛点决定了我们必须快速实施模块整体的拆分和复用,而优雅的设计模式并不适合解决这一类问题。即使从复用性的角度来看,这样的设计也会使得业务开发变得更为复杂、调试困难,对于新人来说难以胜任,最终推广落地困难。

中间层实践

通过多次实践,我们认识到要实现两端代码复用,基础库的统一是必然的工作,是其他一切工作的基础。否则必然导致复杂和难以维护的设计,最终导致两端复用无法快速推进下去。

计算机界有一句名言:“计算机科学领域的任何问题都可以通过增加一个中间层来解决。”(原始版本出自计算机科学家David Wheeler)我们当然有想过通过中间层设计屏蔽两端的基础库差异。例如网络库,外卖App基于Volley实现,外卖频道基于Retrofit实现。我们曾经在Volley和Retrofit之上封装了一层网络框架,对外暴露统一的接口,上层可以切换底层依赖Volley或是Retrofit。但这个中间层并没有上线,最终我们将两端的网络库统一成了Retrofit。这里面有多个原因:首先Retrofit本身就是较高层次的封装,并且拥有优雅的设计模式,理论上我们很难封装一套扩展性更强的接口;其次长期来看底层网络框架变更的风险极低,并且适配网络层的各种插件也是一件费时费力的事情,因此保持网络中间层的性价比极低;此外将两端的网络请求都替换为中间层接口,显然工作量远大于只保留一端的依赖。

通过实践我们认识到,中间层设计是一把双刃剑。如果基础框架本身的扩展性足够强,中间层设计就显得多此一举,甚至丧失了原有框架的良好特性。

平台化实践

好的架构源于不停地衍变,而非设计。对于外卖Android客户端的平台化架构构建也是经历了同样的过程。我们从考虑如何解决代码复用的问题,逐渐的衍变成如何去解决代码复用和平台化的两个问题。而实际上外卖平台化正是解决两端代码复用的一剂良药。我们通过建立外卖平台,将现有的外卖业务降级为一个频道,将外卖业务以aar的形式分别接入到外卖平台和美团平台,这样在解决外卖平台化的同时,代码复用的问题也将得到完美的解决。

平台化架构

经过了整整一年的艰苦奋斗,形成了如图所示的美团外卖Android客户端平台化架构:

image

从底层到高层依次为平台层、业务层和宿主层。

  1. 平台层的内容包括,承载上层的数据通信和页面跳转;提供外卖核心服务,例如商品管理、订单管理、购物车管理等;提供配置管理服务;提供统一的基础设施能力,例如网络、图片、监控、报警、定位、分享、热修、埋点、Crash上报等;提供其他管理能力,例如生命周期管理、组件化等。
  2. 业务层的内容包括,外卖业务和垂直业务。
  3. 宿主层的内容包括,Waimai App壳和美团外卖频道Waimai-channel壳,这一层用于Application的初始化、dex加载和其他各种必要的组件或基础库的初始化。

在构建平台化架构的过程中,我们遇到这样一个问题,如何长久的维持我们平台化架构的层级边界。试想,如果所有的代码都在一个工程里面开发,通过包名、约定去规范层级边界,任何一个紧急的需求都可能破坏层级边界。维持层级边界的最好办法是什么?我们的经验是工程隔离。平台化的每一层都去做工程隔离,业务层的每个业务都建立自己的工程库,实现工程隔离。同时,配套编译脚本,检查业务库之间是否存在相互依赖关系。工程隔离的好处是显而易见的:

  1. 每个工程都可以独立编译、独立打包;
  2. 每个工程内部的修改,不会影响其他工程;
  3. 业务库工程可以快速拆分出来,集成到其他App中。

但工程隔离带来的另一个问题是,同层间的业务库需要通信怎么办?这时候就需要提供业务库通信框架来解决这个问题。

业务库通信框架

在拆分外卖商家业务库的时候,我们就发这样一个案例:在商家页有一个业务,当发现当前商家是打烊的,就会弹出一个浮层,推荐相似的商家列表,而在我们之前划分的外卖子业务库里面,相似商家列表应该是属于页面库里面的内容。那怎么让商家业务库访问到页面库里面的代码呢。如果我们将商家库去依赖页面库,那我们的层级边界就会被打破,我们的依赖关系也会变得复杂。因此我们需要在架构中提供同层间的通信框架,它去解决不打破层级边界的情况下,完成同层间的通信。

汇总同层间通信的场景,大致上可以划分为:页面的跳转、基本数据类型的传递(包括可序列化的共有类对象的传递)、模块内部自定义方法和类的调用。针对上述情况,在我们的架构里面提供了二种平级间的通信方式:scheme路由和美团自建的ServiceLoaders sdk。scheme路由本质上是利用Android的scheme原理进行通信,ServiceLoader本质上是利用的Java反射机制进行通信。

scheme路由的调用如图所示:

image

最终效果:所有业务页面的跳转,都需要通过平台层的scheme路由去分发。通过scheme路由,所有业务都得到解耦,不再需要相互依赖而可以实现页面的跳转和基本数据类型的传递。

serviceloader的调用如图所示:

image

提供方和使用方通过平台层的一个接口作为双方交互的约束。使用方通过平台层的ServiceLoader完成提供方的实现对象获取。这种方式可以解决模块内部自定义方法和类的调用,例如我们之前提到了商家库需要调用页面库代码的问题就可以通过ServiceLoader解决。

外卖内核模块设计

在实践的过程中,我们也遇到业务本身上就不好划分层级边界的业务。大家可以从美团外卖三层架构图上,看出外卖业务库,像商家、订单等,是和外卖的垂类业务库是同级的。而实际上外卖业务的子业务是否应该和垂类业务保持同层是一个目前无法确定的事情。

目前,外卖接入的垂类业务商超业务,是隶属于外卖业务的子频道,它依然依赖着外卖的核心model、核心服务

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值