《从分层架构到微服务架构》是一系列介绍《Fundamentals of Software Architecture》中提到的8种架构模式的文章,这里不会事无巨细地介绍所有的细节,而是会挑选其中关键内容,更多详情请阅读原书。
前言
软件刚出现的时候,还是大型计算机的年代,一个软件系统一般都只会运行在一台机器上。随着软硬件技术的革新,计算机体积和成本逐渐变小,此时工程师们发现一个软件系统只运行在单台机器上会存在各种瓶颈。如果将系统按照功能划分成前端和后端,分别部署在两台服务器上,问题得到了缓解,于是便有了Client/Server架构的出现。
随后,个人电脑的兴起带动了众多富桌面应用(rich desktop application)的出现,它们基于操作系统上的user interface开发,数据则是存储在单独部署的database server上,通过标准的网络协议进行数据通信。这种Desktop + Database Server的架构和C/S架构一样,同属两层架构(two-tier architecture)。
随着90年代互联网的迅速崛起,Browser + Web Server + Database Server的组合也渐渐风靡。Browser为表现层,提供用户交互界面;Web Server为业务层,处理具体的业务逻辑;Database Server为数据层,存储系统数据。三个层次各司其职,这也是大家最熟悉的三层架构(three-tier architecture)。
上述的几种架构模式都属于分层架构(layered architecture)的范畴,分层架构并没有限定一定得有多少个层次,层次的数量可以根据应用场景灵活控制,因此也被称为n-tier architecture。它结构简单,基于此架构进行系统开发成本也很低(很多公司在组织结构上划分为前端工程师、后端工程师、DBA,根据康威定律,这天然就具备了分层架构开发的良好条件),因此它在业界备受欢迎。如果你的团队还不确定选择什么样的架构,又或者为了践行敏捷宣言中的“just starts coding“,那么分层架构会是一个不错的选择。
架构视图
在分层架构中,组件根据功能被划分在不同的层次上,虽然层次的数量和类型并没有被限制,但大多数的分层架构都由以下4层组成:表现层(presentation)、业务层(business)、持久层(persistence)和数据层(database),如下图所示。在一些简单的系统中,持久层的逻辑(如SQL)被嵌入到业务层中,形成了经典的三层架构;而在一些复杂的系统中,也会根据具体的业务划分为五层甚至更多的层次。
前文所述的表现层等4个层次都是逻辑的划分方法,在实际部署时,一般会有下图所示的几种部署形态。形态1中,表现层、业务层和持久层为一个部署单元,而数据层则单独部署,具体表现为一个独立部署的数据库或文件系统;形态2中,表现层被分离出单独部署,业务层和持久层组成一个部署单元,数据层依旧是单独部署的数据库或文件系统;形态3中,包括数据层在内的4层全都在同一个部署单元内,常见于业务简单的系统,它们往往使用的是嵌入式数据库或内存数据库。
分层架构中的每一层都扮演着各自的角色,比如表现层负责处理所有的用户请求和浏览器交互,而业务层则负责执行每次请求下的特定业务逻辑;表现层无需担心从哪里获取用户数据,它只需要将数据以特定的格式在浏览器上显示即可。同样地,业务层也无需关心用户数据从何而来以及如何呈现,它只需从持久层中取出数据,执行特定的业务逻辑(比如聚合数据),然后将结果返回给表现层。
每一层都是特定行为的抽象,这样的职责划分,使得组织能够快速高效地创建出责任模型,围绕各层打造开发团队。
层间隔离
分层架构中的每一层可以是封闭的或者开放的,封闭意味着当一个请求自顶向下在层间传递时,它不能跳过任意的一层。比如,当表现层接收到请求之后,它必须先后经过业务层和持久层才能到达数据层,如下图所示。
对于简单的数据获取类请求,如果让表现层能够直接访问数据层获取数据,无疑是最简单高效的。也即是让业务层和持久层变成开放状态,允许请求在层间传递时跳过此层。那么,究竟是封闭好,还是开放好呢?要解答这个问题,就要回到层间隔离的出发点上。
所谓的层间隔离,旨在降低一个层次上的变化对其他层次的组件的影响,简单来说,就是每个层次对其他层次的功能知道的越少越好。为了达到层间隔离的目的,就需要将每个层次置为封闭的状态。假设表现层能够直接访问持久层,那么持久层的变化将会直接影响到业务层和表现层,这加剧了层间的耦合,导致系统变化的代价高昂。
层间隔离可以降低层次变化对系统的影响,凡事没有绝对,在某些的场景,将特定的层次置为开放的状态也不失为一件好事。考虑以下例子,业务层中存在着一些共享组件承载着业务层公共的功能(比如日志类、审计类、日期和字符串工具类等)。现在有一项架构决策要求表现层不能直接访问这些共享组件,但矛盾的是,原则上表现层是可以直接访问业务层的,这种需要违反原则的决策将会很难落地。
一种解决方法是,新增一个服务层,该层包含了业务层的这些共享组件。因为业务层是关闭的状态,故表现层也就不能访问到这些共享组件了。然而,新增的服务层必须置为开放状态,否则业务层将无法直接访问持久层。新增一个服务层并置为开放状态,这样既落地了架构决策,也不会影响到原有的功能,一举两得。
一些注意事项
在使用分层架构时,需要注意以下两点:
1、做好模块的划分
为分层架构做好模块划分主要是为后续的架构演进做好准备,比如在业务复杂到一定程度后演进为微服务架构时,各个模块可以很自然地演进为微服务。为此,应该避免出现类的继承层次过深的现象,这会导致代码严重的耦合,不利于后续的架构演进。
2、避免掉进sinkhole反模式的陷阱
所谓sinkhole反模式指的是请求只是简单地路过各个层次,并没有做一些业务处理。
比如,表现层接收到一个获取基本用户数据(姓名、地址等)的请求后将它传递到业务层;然而,业务层并没有做任何的业务处理,直接将请求传递到持久层;持久层也仅仅是构造了一个简单的SQL语句,向数据层查询用户数据;最后,数据按照原路返回到表现层,中途没有经过任何的数据汇聚、转换等操作。
sinkhole反模式会导致很多不必要的对象实例化开销,从而增大了系统的内存消耗,并且影响了性能。
然而,一个系统多多少少都会存在一些sinkhole反模式场景,要判断一个系统是否已经彻底掉进sinkhole反模式的陷阱,主要还是看这类业务请求所占的百分比。根据20-80法则,当系统中有超过80%的业务请求是sinkhole类请求时,表示系统已经掉进sinkhole反模式的陷阱,这从侧面也说明该系统已经不再适合分层架构,是时候考虑架构演进了。
综合得分
从综合得分上看,分层架构的Overall cost和Simplicity得分很高,这很大程度上得益于分层架构本身是单体架构,少了很多分布式系统才有的复杂性。但这样导致Deployability得分很低,因为3行代码的改动就足以造成整个系统的重新部署。Testability得分不高也是这个原因,整系统的重新上线通常都需要将测试用例全部执行一遍,多了不少额外的工作量。
Elasticity、Fault tolerance、Scalability这些都是单体架构天然的劣势,自然地,分层架构在这些方面得分都很低。另外,sinkhole反模式的存在也拉低了分层架构在Performance上的得分。
总结
分层架构简单而高效,业界已经有很多成熟的应用,对那些项目刚刚起步,架构师们还没想好要采用哪种架构模式的系统而言,这是非常适合的。在实现分层架构时,我们需要合理地设置各个层次的封闭或开放状态,做好层间隔离,同时也要避免掉进sinkhole反模式陷阱。随着业务的不断扩张,分层架构在可维护性、可测试性、可扩展性等上的短板也会逐步被放大,此时就需要考虑往其他架构模式演进了。
每种架构模式都有其合适的应用场景,只有熟悉常用的几种架构模式,才能设计出更好的软件系统。下一篇文章,我们将继续介绍管道架构。