第三章:分层架构
传统的IT团队结构按照技术领域进行组织,例如演示团队、后端开发团队和数据库团队等。由于大多数架构师、设计师和开发人员对这种结构非常熟悉,分层架构成为大多数商业应用程序开发项目的自然选择。然而,就像所有架构风格一样,它具有优点和缺点,并不适用于所有系统。
描述
在分层架构风格中,组件被组织成水平层,每个层在应用程序中扮演特定角色,例如展示逻辑、业务逻辑和持久化逻辑。尽管层数可能有所不同,但大多数分层架构由四个标准层组成:展示、业务、持久化和数据库(见图3-1)。在某些情况下,业务层和持久化层会合并为一个单一的业务层,特别是当将持久化逻辑(如SQL)嵌入到业务层组件中时。因此,在较小的应用程序中可能只有三个层,并且较大且更复杂的商业应用程序可能包含五个或更多的层。
Figure 3-1. The layered architecture style is a technically partitioned
architecture
分层架构风格的每一层在应用程序中都扮演着特定的角色和责任。例如,表示层负责处理用户界面和浏览器通信逻辑,而业务层则负责执行与请求相关的具体业务规则。架构中的每一层都形成了一个抽象概念,围绕满足特定业务请求所需完成的工作展开。例如,表示层无需关注如何获取客户数据;它只需要以特定格式将信息呈现在屏幕上。同样地,业务层不必关心如何将客户数据格式化为显示在屏幕上或者客户数据来自哪里;它只需要从持久化层获取数据,在数据上执行业务逻辑(比如计算值或聚合数据),并将结果传递给表示层。
通常,层次结构通过命名空间、包结构或目录结构来体现(取决于所使用的实现语言)。例如,在业务层中,客户功能可以表示为app.business.customer,而在展示层中,客户逻辑将表示为app.presentation.customer。在这个例子中,命名空间的第二个节点代表了层级,而第三个节点代表了领域组件。请注意,在所有层级上都重复出现的命名空间的第三个节点(customer)揭示了一种技术上分区的架构方式,在该架构下领域被分散到各个层级。
分层架构风格的一个强大特点是组件之间的关注点分离。特定层中的组件仅处理与该层相关的逻辑。例如,表示层中的组件专注于处理表示逻辑,而业务层中的组件则专注于处理业务逻辑。这种明确划分使得在体系结构中轻松构建有效角色和责任模型,并且当使用明确定义了各个层之间接口和契约时,使用此架构风格进行应用程序开发、测试、管理和维护也变得更加便捷。
关键概念
在这种架构风格中,层可以是开放的或封闭的。请注意图3-2中标记为封闭的每个层次。封闭的层意味着当请求从一个层次移动到另一个层次时,它必须通过下面的一层才能到达下面的下一层。例如,源自表示层的请求必须首先经过业务逻辑层,然后再经过持久化层最终到达数据库层。
那么为什么不允许表示层数直接访问持久化或数据库层数呢?毕竟,从表示曾直接访问数据库比通过一堆不必要的中间步骤来检索或保存数据库信息要快得多。对于这个问题的答案在于一个关键概念------隔离性分级。
隔离层概念意味着在架构的一个层次中进行的更改通常不会影响或干扰其他层次的组件。这种变化仅限于该层内部的组件,可能还包括与之关联的另一层(例如持久化层中包含SQL语句)。如果允许表示层直接访问持久化层,则对持久化层中SQL语句所做的更改将同时影响业务逻辑和表示层,从而导致应用程序具有高度耦合性和大量组件间相互依赖性。这种类型的架构则变得脆弱,并且很难且昂贵地进行修改。
Figure 3-2. With closed layers, the request must pass through that layer
隔离层概念指的是每个层都是相互独立的,因此对于架构中其他层的内部工作几乎没有或者完全没有了解。为了更好地理解这一概念的力量和重要性,可以考虑一个大型重构项目,将演示框架从angular.js转换为react.js。假设在演示层与业务层之间使用的合同(例如模型)保持不变,则业务层不会受到重构影响,并且仍然完全独立于所使用的用户界面框架类型。同样适用于持久化层:如果设计得当,在将关系数据库替换为NoSQL数据库时只会影响到持久化层,而不会对演示或业务层产生任何影响。
尽管封闭的层可以实现隔离层次,从而有助于在架构中隔离变化,但有时候某些层开放也是合理的。例如,假设您想向包含业务层内组件访问的共享服务功能(如数据和字符串工具类或审计和日志记录类)的架构添加一个共享服务层。在这种情况下,创建一个服务层通常是明智之举,因为它从架构上限制了对共享服务仅限于业务层(而不是展示层)。如果没有单独一层,则无法在架构上限制展示逻辑访问这些公共服务,使得管理此访问限制变得困难。
在共享服务层示例中,该层可能位于业务逻辑之下以表示该服务只能被业务逻辑所访问。然而,在这里存在一个问题:业务逻辑不应该需要通过服务护盾才能到达持久性存储区域。这是分布式系统设计中经典的问题,并且可以通过创建开放式护盾来解决。
如图3-3所示,在本例中,由于服务模块是开放式的,因此可以绕过它并直接到达持久性存储模块,这样做是合理的。
Figure 3-3. With open layers, the request can bypass the layer below it
运用开放与封闭层概念来界定架构层与请求流之间关系,为设计师和开发人员提供必要信息,使其了解不同层级在架构中所具备的访问限制。若无法准确记录或传达哪些层级是开放还是封闭(及其原因),往往会导致高度耦合且脆弱性较大的架构,该类结构难以进行测试、维护和部署。
示例
为了阐明分层架构的工作原理,考虑一个来自业务用户的请求,要求检索特定个人的客户信息,如图3-4所示。请注意箭头显示请求向下流动至数据库以检索客户数据,并且响应向上流回屏幕以显示数据。
Figure 3-4. An example of the layered architecture
在这个例子中,客户信息包括客户数据和订单数据(由顾客下的订单)。在此情况下,顾客界面负责接收请求并显示顾客信息。它不了解数据存储位置、检索方法或需要查询多少数据库表才能获取数据。
一旦客户屏幕接收到获取特定个人客户信息的请求,它会将该请求转发给表示层中的客户代理模块。这个模块负责确定业务层中能够处理该请求的模块,并决定如何访问这些模块以及所需数据(合同)。业务层中的客户对象负责聚合所有获取客户信息所需的数据。随后,客户对象模块调用持久化层中的客户DAO(数据访问对象)模块来检索客户数据,并调用订单DAO模块来获取订单信息。这些模块依次执行SQL语句以检索相应数据并传递回业务层中的客户对象。一旦客户对象接收到数据,它会聚合这些数据并将信息传递回给客户代理,然后由此将数据传递给用户展示在用户界面上。
斟酌与分析
分层架构是一种被广泛理解和通用的架构风格,适用于大多数应用程序,并可作为确定最适合应用程序的架构风格时的良好起点。然而,在选择此风格之前,需要考虑几个与架构相关的问题。
首先要注意所谓的"架构陷阱反模式"。该反模式描述了请求在多个层次间简单传递处理,每个层次内没有或只有很少执行逻辑。例如,假设表示层响应用户检索客户数据请求,则表示层将请求传递给业务层,业务层仅将请求传递给持久化层,然后持久化层再向数据库调用简单SQL语句以检索客户数据。数据随后在整个堆栈中原封不动地返回,并未进行任何额外处理、聚合、计算或转换等操作。
每个分层架构都存在一些符合架构陷阱反模式的情况。然而,关键在于对落入此类别请求的百分比进行分析。通常采用80-20法则来确定是否正在经历架构陷阱反模式是一个良好的做法。通常大约有20%的请求作为简单透传处理,而80%的请求与某些业务逻辑相关联。然而,如果您发现这个比例相反,并且大部分请求都是简单透传处理,则可能需要考虑开放一些架构层次,但请记住尽管速度更快,由于缺乏层隔离,控制变更将更加困难。
当初引入时就像今天一样可行的分层架构仍然具备优势。虽然更现代化的分析和设计方法(如领域驱动设计)为开发人员和架构师提供了从领域角度而不是技术角度思考问题的方式,但仍有时候技术上划分体系结构(如分层架构)更加合适。
什么时候考虑这种风格
如果项目或倡议具有重要的预算或时间限制,那么分层架构是值得考虑的。由于分层架构通常被认为是一种单体架构风格,它在远程访问、合同管理以及前一章中描述的分布式计算谬误方面没有复杂性。此外,大多数开发人员和架构师都熟悉分层架构,使其更容易理解和实施。
另一个考虑使用分层架构的原因是大部分变化仅限于应用程序内特定的层级。例如,仅影响用户界面规则的变化、仅涉及用户界面外观的变化、迁移到新的用户界面框架以及迁移到新类型数据库等都只影响到体系结构中特定的某个层级,这样可以更容易地隔离受到变化影响的组件。
由于分层架构是一种技术上的分区架构,因此如果团队结构也按照技术进行划分,这种架构将非常适合。换言之,如果整体团队结构组织为展示(UI)开发人员、后端开发人员、共享服务团队和数据库团队等,那么与该架构风格(展示层、业务层、持久化层等)的整体划分相吻合。这种对齐符合康威定律。
什么时候不要考虑这种风格
尽管有许多理由可以考虑前一节中描述的分层架构,但不幸的是,还存在更多原因不应采用分层架构。
首先,如果您对应用程序具有高度的运营关注,例如可扩展性、弹性、容错性和性能等方面,则不适合采用分层架构。因为分层架构倾向于单体式架构,在使用这种架构风格构建的应用程序通常很难进行扩展。虽然通过将各个层拆分为独立的物理部署和/或在多个虚拟机中创建应用程序的独立实例来实现分层架构的扩展性,但这样做非常昂贵且低效,因为必须对100% 的应用功能进行扩展。此外,分层架构并不具备良好的容错能力------任何部分发生致命崩溃都会导致整个应用功能崩溃。
另一个避免采用分层架构的原因是当大部分变化发生在领域级别而非技术级别时。假设您被要求在电影流媒体应用程序中给客户"我的电影列表"添加到期日期(即客户排队准备稍后观看的电影)。这项新功能首先需要更改数据库模式,然后修改持久化层中SQL语句,并调整业务规则和业务约定(例如到期前多长时间、列表中电影过期时如何处理等),最后还需修改表示层以显示每部电影旁边的到期日期。
在分析对"我的电影列表"功能进行相对简单更改时,我们注意到每个层次的架构都会受到影响并需要进行调整。在大型系统中,特别是那些具有技术上划分团队的系统中,这可能涉及多个团队(如UI、后端和数据库)之间的协调来实现此更改。这不仅会影响整体敏捷性(即快速应对变化能力),还会影响完成此更改所需的总时间和工作量。
最后,在跨职能领域团队组成整体团队结构下(即单一团队拥有专注于应用程序特定领域内UI、后端和数据库专业知识),分层架构可能不适合,因为技术上划分的架构结构与领域划分的团队结构不一致。
架构特征
图3-5中的图表综述了分层架构在整体能力(即架构特征)方面的评级。一颗星表示该架构特征得到的支持不足,而五颗星表示它非常适合该特定的架构特征。
Figure 3-5. Architecture characteristics star ratings for the layered
architecture