从分层架构到六边形架构
在计算机领域很多概念都是抽象的,为了能够清晰地划分这些抽象的复杂概念,随处可见各种分层式的设计方式。比如计算网路的七层通信协议,把复杂的网络通信划分成更容易管理的层,明确定义各层的职责范围,各层都可以采用最合适的技术来实现。当任何一层发生变化时,只要层间接口关系保持不变,则在这层以上或以下各层均不受影响。
在常见的软件系统的架构中也有一种4层架构设计,通常从前到后会划成:用户接口层,应用层,领域层,基础设施层。
- 用户接口层只需要关注如何同使用者打交道,不需要关注核心的业务逻辑,例如把用户提交的数据经过转码处理后提供给应用层。
- 应用层的关注点是如何组装核心的领域层来提供复杂的组合服务,例如应用层可以从领域层的用户仓库获取用户信息,从领域层的订单仓库获取订单信息,然后在应用层把用户信息和订单信息组装在一起返回给用户接口层
- 领域层处理的业务问题,是更加内聚的领域业务,不同业务之间要做到低耦合。例如在用户聚合中就只处理用户相关的逻辑,不应该包含订单,支付这些其它的业务逻辑。
- 基础设施层是不包含任何业务逻辑的底层支持,例如生成pdf或者excel,或者把数据持久化到数据库的ORM框架
但是这样的分层架构会导致其他三层尤其是领域层对基础设施层产生依赖。这种依赖会导致当基础设施层发生变更的时候,会导致其它三层也要跟随变动,这是我们不希望放生的事情,尤其是涉及核心业务逻辑的领域层,我们不希望因为某种生成excel或者pdf的技术发生变化而要修改领域层。
那么如果考虑对基础设施进行解耦的方法,可供选择的的方式有依赖倒置,在每一层定义对应的技术接口,然后在基础设施层实现这个接口,再通过一些依赖注入框架在运行式把实现注入进来,这样就把原来其它三层依赖基础设施层的情况倒置成了基础设施层依赖其它三层了。
有趣的是,当我们在分层架构中采用依赖倒置原则时,我们会发现事实上已经不存在分层的概念了。无论高层还是低层,都只依赖于抽象的接口。如果我们这时把分层架构推平,这就是在微服务领域非常著名的六边形架构。
六边形架构是Alistair Cockburn提出的,其实是把原来的分层架构转换了一下视角,从原来的上下结构变成了内外结构。六边形架构把原来一个系统的输入和输出都统一来处理,输入输出对接的都是外部系统,都需要一个适配器来进行转换。所以不论是处理输入的HTTP请求,还是处理输出的数据库存储都应该有一个适配器来负责接入。适配器会调用负责业务逻辑组合的应用层,而应用层负责调用核心的领域层。
相比于应用层,领域层通常更少变化。在这样的架构关系下,当需要修改一个负责连接数据库的ORM工具时,或者是应用层返回的订单信息需要增加物流状态时,都不需要去修改最核心的领域层。
为什么是六边形
不知道大家在看到六边形架构这个名字的时候会不会产生一个疑问,就说是为什么是六边形,不是七边形,五边形。
这个问题其实困扰了我蛮久,但是当我在郑也夫老师的社会学专题50讲里面找到了答案。六边形这个概念源自于城市规划,有两个德国人,一个是克里斯塔勒,他的研究兴趣是人口密度和商品销售关系。他1933年写出一部书,叫做《德国南部中心地原理》。另外一个是廖什 ,他的兴趣在于研究空间秩序,他在1940年写出一部书叫做《经济空间秩序》。
这两个人各自独立的提出了六边形理论:
每个商家销售的范围是一个圆。因为人们买东西有交通成本,所以导致着以一个卖点为中心,销售范围是围绕着这个中心画的一个圆。当人口越来越多的时候,商家也就越来越多,那个圆形越来越多,导致着一个圆形跟另一个圆形相互挤压,最后压成了一个蜂巢一样的紧密相连的正六边形的组合体。
最低级的销售点是集市,销售一些最通常的日用品,构成了最小的六边形拼凑出来的平面。稍大一点的城市会卖衣服、鞋等等中档的东西,它们就是中型的六边形。大的都市会卖一些很高档的东西,珠宝、珍贵的皮毛等等。这些大的正六边形拼凑出一个大的平面。并且在一个广大的地域中,大都市销售区是大六边形的对接,其中包含着中小六边形。
除了人类的集市,还有一种自然界的天然六边形就是蜂巢。但是你有想过这个问题吗,蜜蜂在搭建蜂巢的时候又没有测量工具,它们是怎么搭建出来比例那么标准的的六边形蜂巢的呢。
其实蜜蜂根本就不曾想过要搭建六边形,蜜蜂在搭建蜂巢的时候是按照记得身体的大小围出了圆形的一个个蜂巢,请看下图:
然后这一个个小圆经过蜂巢自己本身的拉伸和风化,就变成了六边形。蜜蜂本来就是要造圆形巢的,但是由于自然界本身的趋于稳定的焓变规则,软的圆形巢就拉成六边形了。
[图片上传失败...(image-750f2e-1541523534115)]
那么为什么圆形会被拉伸成六边形呢?阿尔法小分队有一个视频专门介绍了这个问题的数学原理(视频传送门)。在视频中用小时候经常玩的泡泡做了一个简单的实验,当四个泡泡凑仔一起的的时候,在泡泡边界上出现的所有夹角都是120度。所以当很多个泡泡挤在一起的时候,由于在拉扯力的作用下边界夹角都是120度,所以一个个泡泡就“天然的”变成了六边形。而蜂巢,人类集市是遵循这个自然规律。
回到微服务架构,虽然“六边形架构”这个概念是Alistair Cockburn创造出来的,但是由于六边形代表了自然规律,所以我认为六边形架构是“纯天然”的。而且如果我们把创建的微服务看成一个个小泡泡,那么把这些小泡泡排列在一起以后,自然法则会让他们变成一个个六边形。而不可能是五变形或者七边形。
介绍完了六边形的故事,让我们进入六边形架构的微服务内部,看看里面的代码是什么样子的。
一个六边形架构的代码结构示例
首先在一个微服务的根目录上应该只有3个包,adapter,application和domain。而且还要严格限制依赖关系:
- domain中不可以依赖任何application和adapter中的代码
- application中不可以依赖任何adapter中的代码
假设一个简单的场景:用户从界面提交一个支付请求,最终这个支付请求的相关信息会被存入数据库中。
接受请求这段很好实现,在adapter层里有一个controller来处理请求,把请转换成领域模型payment,然后调用application层SubmitPaymentApplication的submit方法来提交payment。然后SubmitPaymentApplication会调用domain层的PaymentRepository. 这时候会有一个麻烦的问题,通常来说Repository里面都会依赖具体的ORM工具,比如hibernate或者Mybatis。但是前面说过我们希望Domain层是不要有任何外部依赖的,所以解决这个问题的方法就是在只在domain层定义PaymentRepository的接口,然后在Adapter层使用ORM工具实现这个接口。
ORM工具是在面向对象程序中进行数据库持久化的好用工具,而且现在流行的ORM工具都提供了从数据库表到实体类的自动生成功能,让开发者尽可能的减少机械化的工作。但是这些好用的工具带来的一个问题就是实体越来越贫血,实体里面只是有一堆属性和对应的get/set方法而已。但是在六边形架构里面,由于domain层的实体不需要由ORM生成,数据库持久化相关的工作都在外面的adapter层中完成,所以开发者就可以放心的在domain中使用枚举等各种编程语言的特性,还可以在实体中放心的实现各种本来就属于实体的行为。
思辨小结
这几年由于微服务的原因六边形架构也变得热了起来。但是要真的用好六边形架构,我们需要对这个概念有一个更深入的理解:知道它是怎么提出来的?它解决的是什么问题?为什么是六条边?弄清楚了这些,在应用六边形架构的时候才能清楚地知道什么逻辑只能属于哪一层,哪一层不能依赖哪一层。坚持这些原则的价值是:当有新需求或者需求发生变更时,重用代码和修改代码都是让人愉悦的事情。
把使用ORM的数据库持久化实现放在adapter的一个可能问题是,由于domain层的实体更贴近业务,所以这些实体模型无法直接给ORM使用,这就需要在adapter里面再实现一套专门对应数据库表的对象模型出来,并在adapter里面吧domain的实体转换成这些模型再进行数据库操作。这样看起来好像会有两套冗余的业务模型,一个在domain中,一个在adapter里面。但其实只有domain中的才是真正的业务模型,adapter里面的模型只是数据库表的一个映射对象而已。这种方式很好地把领域模型和持久化相关实现进行了解耦,我们可以完全抛开数据库或者ORM的技术限制,去设计能够真正和业务对应的领域模型。