分层架构风格,也称为n层架构风格,是最常见的架构风格之一。这种风格的架构是大多数应用程序的事实标准,主要是因为它简单、友好并且成本低。根据康威定律,使用分层架构开发应用程序也是一种非常自然的方式,设计系统的组织最终会产生与组织架构相同的设计。在大多数组织中,都有用户界面(UI)开发人员、后端开发人员、规则开发人员和数据库专家(DBA)这些角色。这些组织层很好地适应了传统分层架构的各个层,使其成为许多业务应用程序的自然选择。分层架构风格也分为几种架构反模式,包括隐含架构反模式和偶然架构反模式。如果一个开发人员或架构师不确定他们正在使用哪种架构风格,或者一个敏捷开发团队“刚开始编码”,那么很有可能他们正在采用分层架构风格。
拓扑结构
组件在分层架构中被组织成逻辑水平层,每个层在应用中执行特定的角色(例如表示逻辑或业务逻辑)。尽管对于必须存在的层的数量和类型没有特定的限制,但是大多数分层体系结构由四个标准层组成:表示层、业务层、持久层和数据库层,如图10-1所示。在某些情况下,业务层和持久性层被组合成一个业务层,特别是当持久性逻辑(如SQL或HSQL)嵌入到业务层组件中时。因此,较小的应用可能只有三层,而更大和更复杂的业务应用可能包含五层或更多。
图10-2从物理分层(部署)的角度说明了各种拓扑结构变体。第一个变体将表示层、业务层和持久层组合到一个部署单元中,数据库层通常表示为一个单独的外部物理数据库(或文件系统)。第二个变体在物理上将表示层划分为单独的部署单元,业务层和持久层合并为第二个部署单元。同样,使用此变体,数据库层在物理上通常通过外部数据库或文件系统进行分离。第三个变体将所有四个标准层组合到一个部署中,包括数据库层。对于具有内部嵌入式数据库或内存数据库的较小应用程序,此变体可能很有用。许多内部(“免费”)产品都是使用第三种变体建设并交付给客户的。
分层架构风格的每一层在架构中都有特定的角色和责任。例如,表示层将负责处理所有用户界面和浏览器通信逻辑,而业务层将负责执行与请求相关联的特定业务规则。架构中的每一层围绕满足特定业务请求所需完成的工作形成一个抽象。例如,表示层不需要知道或担心如何获取客户数据;它只需要在屏幕上以特定格式显示这些信息。同样,业务层也不需要关心如何格式化客户数据以便在屏幕上显示,甚至不需要知道客户数据来自何处;业务层只需要从持久层获取数据,对数据执行业务逻辑(例如计算值或聚合数据),并将这些信息传递到表示层。分层架构风格中的关注概念分离使得在架构中构建有效的角色和责任模型变得容易。例如,表示层中的组件只处理显示逻辑,而驻留在业务层中的组件只处理业务逻辑。这使得开发人员能够利用他们特定的技术专长来专注于特定领域的技术方面的内容(例如表示逻辑或持久性逻辑)。然而,这种好处的代价是缺乏整体灵活性(快速响应变化的能力)。
分层架构是从技术上划分的架构(与领域划分的架构相反)。组件分组不是按领域(如客户)分组,而是按它们在架构中的技术角色(如表示层或业务层)分组。这导致任何特定的业务领域都会分布在架构的所有层中。例如,“客户”领域逻辑包含在表示层、业务层、规则层、服务层和数据库层中,因此很难更改该领域的逻辑。因此,领域驱动设计方法不太适用于使用分层架构风格。
隔离层
分层架构风格中的每个层可以是封闭的,也可以是开放的。封闭层意味着当一个请求自上而下地从一个层移动到另一个层时,请求不能跳过任何层,而是必须通过它下面的层到达下一层(如图10-3)。例如,在一个封闭的分层架构中,来自表示层的请求必须首先经过业务层,然后到达持久层,最后才能到达数据库层。
在图10-3中,表示层绕过任何不必要的层(在21世纪初被称为快速通道阅读器模式)直接访问数据库以进行简单的检索请求会更快、更容易。要做到这一点,业务层和持久层必须是开放的,允许请求绕过其他层。开放层和封闭层哪个更好?这个问题的答案在于一个被称为隔离层的关键概念。
隔离层概念意味着在架构的一个层中所做的更改通常不会影响其他层中的组件,只要这些层之间的契约保持不变。每一层都独立于其他层,因此对架构中其他层的内部工作原理知之甚少或一无所知。但是,为了支持隔离层,与请求的流动主干相关的层必须是封闭的。如果表示层可以直接访问持久层,那么对持久层所做的更改将同时影响业务层和表示层,从而产生一个耦合非常紧密的应用,组件之间具有层间的相互依赖关系。这种类型的架构会变得非常脆弱,变更会非常困难而且成本高昂。
隔离层概念还允许在不影响任何其他层的情况下替换架构中的任何层(同样,假设定义良好的契约和使用业务委托模式)。例如,您可以利用分层架构风格中的隔离层概念,在不会影响应用中的任何其他层的情况下将旧的JavaServer Faces(JSF)表示层替换为React.js。
添加层
虽然封闭层有助于隔离层,因此有助于隔离架构中的更改,但有时某些层开放是有意义的。例如,假设业务层中有共享对象,这些对象包含业务组件的公共功能(例如日期和字符串实用程序类、审计类、日志类等等)。假设有一个架构决策,表示层不能使用这些共享业务对象。这个约束如图10-4所示,虚线从表示层组件到业务层中的共享业务对象。这个场景很难管理和控制,因为在架构上,表示层可以访问业务层,因此可以访问该层中的共享对象。
从架构上强制执行此限制的一种方法是向架构添加一个包含所有共享业务对象的新服务层。现在添加这个新的层从架构上限制表示层访问共享业务对象,因为业务层是封闭的(见图10-5)。但是,新的服务层必须标记为“开放”;否则业务层将被迫通过服务层来访问持久层。将服务层标记为“开放”允许业务层访问该层(如实心箭头所示),或者绕过该层并向下转到下一层(如图10-5中的虚线箭头所示)。
利用开放层和封闭层的概念有助于定义架构层次和请求流之间的关系。它还为开发人员提供了了解架构中的各种层的访问限制的必要信息和指导。如果不能记录或正确地传达架构中的哪些层是开放的和封闭的(以及为什么),通常会导致紧密耦合和脆弱的架构,这些架构将很难测试、维护和部署。
其他考虑事项
当还不知道最终将使用哪种架构风格时,分层架构可以作为大多数应用的一个良好的起点。在许多微服务工作的普遍实践中,当架构师仍在确定微服务是否是正确的架构选择时,但开发必须开始。然而,当使用这种技术时,一定要将重用保持在最低限度,并使对象层次结构(继承树的深度)做够的浅,以便保持良好的模块化水平。这将有助于以后转向另一种架构风格。
对于分层架构,需要注意的一点是不要陷入污水池反模式。当请求以简单的传递处理方式从一层移动到另一层,而每个层中并没有执行业务逻辑时,就会出现这种反模式。例如,假设表示层响应用户的一个简单请求,以检索基本的客户数据(例如名称和地址)。表示层将请求传递给业务层,业务层只将请求传递给规则层,而规则层只将请求传递给持久层,后者对数据库层进行简单的SQL调用以检索客户数据。然后,数据将一直回传上去,而不需要额外的处理或逻辑来聚合、计算、应用规则或转换数据。这会导致不必要的对象实例化和处理,从而影响内存消耗和性能。
每一个分层架构都至少有一些场景陷入架构污水池反模式。确定是否陷入架构污水池反模式的关键是分析属于这一类的请求的百分比。80-20规则通常可以作为很好的判断实践。例如,如果只有20%的请求是污水池,则可以接受。但是,如果80%的请求都是污水池,那么对于问题域来说分层架构并不是合适的架构风格。另一种解决架构污水池反模式的方法是让架构中的所有层都开放,当然,也要意识到,代价是在架构中管理变更的难度会增加。
为什么使用分层架构风格
分层架构风格是小型、简单的应用程序或网站的好选择。
对于预算和时间限制非常紧张的情况,分层架构也是一个很好的架构选择,尤其是在开始阶段。由于架构简单性和开发人员和架构师之间对这种架构的熟悉度,分层架构可能是成本最低的架构风格之一,有助于小型应用的开发。当架构师仍在分析业务需求并且不确定哪种架构风格是最合适的时候,分层架构风格也是一个不错的选择。
随着使用分层架构风格的应用程序的不断迭代,诸如可维护性、敏捷性、可测试性和可部署性等特性将受到不利影响。因此,使用分层架构的大型应用和系统可能更适合使用其他更模块化的架构风格。
架构特性评级
特性评级表中的一星级评级(如图10-6所示)意味着特定的架构特性在某种架构中没有得到很好的支持,而五星评级意味着架构特性是某种架构风格中最强大的特性之一。记分卡中确定的每个特性的定义见第4章。
成本低廉和简单性是分层架构风格的主要优势。分层架构本质上是一个单体,它不具有与分布式架构风格相关的复杂性,它简单易懂并且构建和维护的成本相对较低。然而,值得注意的是,随着整体分层架构变得越来越大,从而变得越来越复杂,这些评级开始迅速降低。
这种架构风格的可部署性和可测试性都非常低。可部署性评级低是由于部署成本高、风险高和缺乏频繁部署能力。在分层架构中对一个类文件进行简单的三行更改需要重新部署整个部署单元,原来的改动暗中会引起潜在的数据库更改、配置更改或其他编码更改。此外,这简单的三行更改通常会引起其他几十处的更改,因此会增加部署的风险(也会增加部署的频率)。低可测试性评级也反映了这种情况;通过简单的三行更改,大多数开发人员不会花费数小时来执行整个回归测试套件(即使测试套件一开始就存在),尤其是在同一时间对单体应用还有其他几十处更改的情况下。我们给可测试性一个双星评级(而不是一星),因为它能够对组件(甚至整个层)使用mock或者stub模拟测试,这简化了整个测试工作。
在这种架构风格中,总体可靠性为中等(三星级),主要是由于没有在大多数分布式架构中存在的网络流量、带宽和延迟问题。由于单体应用部署的本质,再加上可测试性(测试的完整性)和部署风险的低评级,我们只对分层架构的可靠性给出了三星级的评级。
分层架构的弹性和可扩展性非常低(一星),主要是由于单体部署和缺乏架构模块化能力。虽然在一个单体应用内实现某些功能的扩展是可能的,但这种工作通常需要非常复杂的设计技术,如多线程、内部消息传递和其他在这种架构中不适合使用的并行处理实践和技术。然而,由于用户界面、后端处理和数据库的单一性,分层架构始终是一个单一的系统量子,因此应用只能在单个量子的基础上扩展到一定的程度。
性能一直是分层架构的一个有趣的特性。我们只给了它两颗星评级,这种架构风格因为缺乏并行处理、封闭分层和架构污水池反模式的原因根本不适合于高性能系统。与可扩展性一样,性能也可以通过缓存、多线程等来解决,但这并不是这种架构风格的自然特征;架构师和开发人员必须通过大量的工作才能实现这一切。
由于单体部署和缺乏架构模块性,分层架构不支持容错。如果分层架构的一小部分发生内存不足问题,则整个应用单元将受到影响并崩溃。此外,由于大多数单体应用普遍存在较高的平均恢复时间(MTTR),总体可用性受到影响,启动时间从小型应用的2分钟,到大多数大型应用的15分钟或更长。