平台化架构
经过了整整一年的艰苦奋斗,形成了如图所示的美团外卖Android客户端平台化架构:
从底层到高层依次为平台层、业务层和宿主层。
- 平台层的内容包括,承载上层的数据通信和页面跳转;提供外卖核心服务,例如商品管理、订单管理、购物车管理等;提供配置管理服务;提供统一的基础设施能力,例如网络、图片、监控、报警、定位、分享、热修、埋点、Crash上报等;提供其他管理能力,例如生命周期管理、组件化等。
- 业务层的内容包括,外卖业务和垂直业务。
- 宿主层的内容包括,Waimai App壳和美团外卖频道Waimai-channel壳,这一层用于Application的初始化、dex加载和其他各种必要的组件或基础库的初始化。
在构建平台化架构的过程中,我们遇到这样一个问题,如何长久的维持我们平台化架构的层级边界。试想,如果所有的代码都在一个工程里面开发,通过包名、约定去规范层级边界,任何一个紧急的需求都可能破坏层级边界。维持层级边界的最好办法是什么?我们的经验是工程隔离。平台化的每一层都去做工程隔离,业务层的每个业务都建立自己的工程库,实现工程隔离。同时,配套编译脚本,检查业务库之间是否存在相互依赖关系。工程隔离的好处是显而易见的:
- 每个工程都可以独立编译、独立打包;
- 每个工程内部的修改,不会影响其他工程;
- 业务库工程可以快速拆分出来,集成到其他App中。
但工程隔离带来的另一个问题是,同层间的业务库需要通信怎么办?这时候就需要提供业务库通信框架来解决这个问题。
业务库通信框架
在拆分外卖商家业务库的时候,我们就发这样一个案例:在商家页有一个业务,当发现当前商家是打烊的,就会弹出一个浮层,推荐相似的商家列表,而在我们之前划分的外卖子业务库里面,相似商家列表应该是属于页面库里面的内容。那怎么让商家业务库访问到页面库里面的代码呢。如果我们将商家库去依赖页面库,那我们的层级边界就会被打破,我们的依赖关系也会变得复杂。因此我们需要在架构中提供同层间的通信框架,它去解决不打破层级边界的情况下,完成同层间的通信。
汇总同层间通信的场景,大致上可以划分为:页面的跳转、基本数据类型的传递(包括可序列化的共有类对象的传递)、模块内部自定义方法和类的调用。针对上述情况,在我们的架构里面提供了二种平级间的通信方式:scheme路由和美团自建的ServiceLoaders sdk。scheme路由本质上是利用Android的scheme原理进行通信,ServiceLoader本质上是利用的Java反射机制进行通信。
scheme路由的调用如图所示:
最终效果:所有业务页面的跳转,都需要通过平台层的scheme路由去分发。通过scheme路由,所有业务都得到解耦,不再需要相互依赖而可以实现页面的跳转和基本数据类型的传递。
serviceloader的调用如图所示:
提供方和使用方通过平台层的一个接口作为双方交互的约束。使用方通过平台层的ServiceLoader完成提供方的实现对象获取。这种方式可以解决模块内部自定义方法和类的调用,例如我们之前提到了商家库需要调用页面库代码的问题就可以通过ServiceLoader解决。
外卖内核模块设计
在实践的过程中,我们也遇到业务本身上就不好划分层级边界的业务。大家可以从美团外卖三层架构图上,看出外卖业务库,像商家、订单等,是和外卖的垂类业务库是同级的。而实际上外卖业务的子业务是否应该和垂类业务保持同层是一个目前无法确定的事情。
目前,外卖接入的垂类业务商超业务,是隶属于外卖业务的子频道,它依然依赖着外卖的核心model、核心服务,包括商品管理、订单管理、购物车管理等,因此目前它和外卖业务的商家、订单这样的子业务库同层是没有问题的。但随着商超业务的发展,商超业务未来可能会建设自己的商品管理、订单管理、购物车管理的服务,那么到时商超业务就会上升到和外卖业务一样同层的业务。这时候,外卖核心管理服务,处在平台层,就会导致架构的层级边界变得不再清晰。
我们的解决办法是通过设计一个属于外卖业务的内核模块来适应未来的变化,内核模块的设计如图:
- 内圈为基础模型类,这些模型类构成了外卖核心业务(从门店→点菜→购物车→订单)的基础;
- 中间圈为依赖基础模型类构建的基础服务(CRUD);
- 最外圈为外卖的各维度业务,向内依赖基础模型圈和外卖基础服务圈。
如果未来确定外卖平台需要接入更多和外卖平级的业务,且最内圈都完全不一样,我们将把外卖内核模块上移,在外卖业务子库下建立对内核模块的依赖;如果未来只是有更多的外卖子业务的接入,那就继续保留我们现在的架构;如果未来接入的业务基础模型类一样,但自己的业务服务需要分化,那么我们将对保留内核模块最核心的内圈,并抽象出服务层由外卖和商超上层自己实现真正的服务。
业务库拆分
在拆分业务库的时候,我们面临着这样的问题:业务之间的关系是较为复杂的,如何去拆分业务库,才是较为合理的呢?一开始我们准备根据外卖业务核心流程:页面→商家→下单,去拆分外卖业务。但是随着外卖子频道业务的快速发展,子频道业务也建立了自己的研发团队,在页面、商家、下单等环节,也开始建立自己的页面。如果我们仍然按照外卖下单的流程去拆分库,那在同一个库之间,就会有外卖团队和外卖子频道团队共同开发的情况,这样职责边界很不清晰,在实际的开发过程中,肯定会出现理不清的情况。
我们都知道软件工程领域有所谓的康威定律:
Organizations which design systems are constrained to produce designs which are copies of the communication structures of these organizations