《软件架构模式》的完整报告。
https://www.oreilly.com/content/software-architecture-patterns/
介绍
开发人员在没有正式架构的情况下开始编写应用程序是很常见的。如果没有明确且定义良好的架构,大多数开发人员和架构师将诉诸事实上的标准传统分层架构模式(也称为 n 层架构),通过将源代码模块分成包来创建隐式层。不幸的是,这种做法通常会导致一堆无组织的源代码模块,这些模块缺乏明确的角色、职责和相互关系。这通常被称为大泥球架构反模式。
缺乏正式架构的应用程序通常紧密耦合、脆弱、难以更改,并且没有明确的愿景或方向。因此,如果不完全了解系统中每个组件和模块的内部工作原理,就很难确定应用程序的架构特征。有关部署和维护的基本问题很难回答:架构是否可扩展?应用程序的性能特征是什么?应用程序对变化的响应有多容易?应用程序的部署特征是什么?架构的响应能力如何?
架构模式有助于定义应用程序的基本特征和行为。例如,某些架构模式自然适合高度可扩展的应用程序,而其他架构模式自然适合高度敏捷的应用程序。了解每种架构模式的特征、优势和劣势对于选择满足您特定业务需求和目标的架构模式是必不可少的。
作为一名架构师,您必须始终证明您的架构决策的合理性,尤其是在选择特定架构模式或方法时。本报告的目的是为您提供足够的信息来做出并证明该决策的合理性。
分层架构
最常见的架构模式是分层架构模式,也称为 n 层架构模式。此模式是大多数 Java EE 应用程序的事实标准,因此为大多数架构师、设计人员和开发人员所熟知。分层架构模式与大多数公司中发现的传统 IT 通信和组织结构非常吻合,因此成为大多数业务应用程序开发工作的自然选择。
模式描述
分层架构模式中的组件被组织成水平层,每层在应用程序中扮演特定角色(例如,表示逻辑或业务逻辑)。尽管分层架构模式没有指定模式中必须存在的层的数量和类型,但大多数分层架构都包含四个标准层:表示层、业务层、持久层和数据库层(图 1-1)。在某些情况下,业务层和持久层会合并为一个业务层,特别是当持久性逻辑(例如,SQL 或 HSQL)嵌入在业务层组件中时。因此,较小的应用程序可能只有三层,而较大且更复杂的业务应用程序可能包含五层或更多层。
分层架构模式的每一层在应用程序中都有特定的角色和职责。例如,表示层负责处理所有用户界面和浏览器通信逻辑,而业务层负责执行与请求相关的特定业务规则。架构中的每一层都围绕满足特定业务请求所需完成的工作形成抽象。例如,表示层不需要知道或担心如何获取客户数据;它只需要以特定格式在屏幕上显示该信息。同样,业务层不需要关心如何格式化客户数据以在屏幕上显示,甚至不需要关心客户数据来自哪里;它只需要从持久层获取数据,对数据执行业务逻辑(例如,计算值或聚合数据),并将该信息传递给表示层。

分层架构模式的强大功能之一是组件之间的关注点分离。特定层内的组件仅处理与该层相关的逻辑。例如,表示层中的组件仅处理表示逻辑,而业务层中的组件仅处理业务逻辑。这种类型的组件分类使您可以轻松地在架构中构建有效的角色和职责模型,并且由于组件接口定义明确且组件范围有限,因此使用此架构模式开发、测试、管理和维护应用程序也变得容易。
关键概念
请注意,在图 1-2中,架构中的每一层都标记为已 关闭。这是分层架构模式中非常重要的概念。关闭层意味着,当请求从一层移动到另一层时,它必须经过紧邻其下的一层才能到达该层之下的下一层。例如,来自表示层的请求必须先经过业务层,然后到达持久层,最后才能到达数据库层。

那么,为什么不让表示层直接访问持久层或数据库层呢?毕竟,从表示层直接访问数据库比仅仅为了检索或保存数据库信息而经过一堆不必要的层要快得多。这个问题的答案在于一个称为 隔离层的关键概念。
隔离层概念意味着在架构的某一层中所做的更改通常不会影响其他层中的组件:更改仅对该层中的组件以及可能的另一个相关层(例如包含 SQL 的持久层)起作用。如果允许表示层直接访问持久层,则对持久层中 SQL 所做的更改将同时影响业务层和表示层,从而产生一个紧密耦合的应用程序,组件之间具有许多相互依赖关系。这种类型的架构很难更改,而且更改成本很高。
隔离层概念还意味着每个层都独立于其他层,因此对架构中其他层的内部工作知之甚少或一无所知。要理解这一概念的强大功能和重要性,请考虑将表示框架从 JSP(Java Server Pages)转换为 JSF(Java Server Faces)的大规模重构工作。假设表示层和业务层之间使用的契约(例如模型)保持不变,则业务层不受重构的影响,并且完全独立于表示层使用的用户界面框架类型。
虽然封闭的层有助于隔离层,从而有助于隔离架构内的变化,但有时某些层是开放的。例如,假设您想要向包含业务层内组件访问的公共服务组件的架构添加一个共享服务层(例如,数据和字符串实用程序类或审计和日志记录类)。在这种情况下,创建服务层通常是一个好主意,因为从架构上讲,它将对共享服务的访问限制在业务层(而不是表示层)。如果没有单独的层,则从架构上讲,没有任何内容可以限制表示层访问这些公共服务,这使得管理此访问限制变得困难。
在这个例子中,新的服务层可能位于 业务层之下 ,以表明此服务层中的组件无法从表示层访问。然而,这带来了一个问题,即业务层现在需要通过服务层才能到达持久层,这完全没有意义。这是分层架构的一个老问题,通过在架构内创建开放层可以解决这个问题。
如图1-3所示,本例中的服务层被标记为开放,这意味着请求可以绕过这个开放层,直接进入其下方的层。在下面的例子中,由于服务层是开放的,业务层现在可以绕过它,直接进入持久层,这非常合理。

利用开放层和封闭层的概念有助于定义架构层与请求流之间的关系,并为设计人员和开发人员提供必要的信息,以了解架构内的各个层访问限制。如果无法记录或正确传达架构中的哪些层是开放的和封闭的(以及原因),通常会导致架构紧密耦合且脆弱,很难测试、维护和部署。
图案示例
为了说明分层架构的工作原理,请考虑业务用户提出的检索特定个人的客户信息的请求,如图1-4所示。黑色箭头表示请求向下流向数据库以检索客户数据,红色箭头表示响应向上流回屏幕以显示数据。在此示例中,客户信息包括客户数据和订单数据(客户下达的订单)。
客户屏幕负责接受请求并显示客户信息。它不知道数据在哪里,如何检索数据,也不知道必须查询多少个数据库表才能获取数据。一旦客户屏幕收到获取特定个人客户信息的请求,它就会将该请求转发到客户委托模块。该模块负责了解业务层中的哪些模块可以处理该请求,以及如何到达该模块以及它需要哪些数据(合同)。业务层中的客户对象负责聚合业务请求所需的所有信息(在本例中为获取客户信息)。该模块调用 持久层中的客户 dao (数据访问对象)模块来获取客户数据,并调用订单 dao模块来获取订单信息。这些模块依次执行 SQL 语句来检索相应的数据并将其传递回业务层中的客户对象。一旦客户对象收到数据,它就会聚合数据并将该信息传递回客户委托,然后客户委托将数据传递到客户屏幕以呈现给用户。

从技术角度来看,这些模块的实现方式实际上有几十种。例如,在 Java 平台中,客户屏幕可以是 (JSF) Java Server Faces 屏幕,再加上客户委托作为托管 bean 组件。业务层中的客户对象可以是本地 Spring bean 或远程 EJB3 bean。上例中所示的数据访问对象可以实现为简单的 POJO(普通旧式 Java 对象)、MyBatis XML Mapper 文件,甚至可以实现为封装原始 JDBC 调用或 Hibernate 查询的对象。从 Microsoft 平台的角度来看,客户屏幕可以是使用 .NET 框架访问业务层中的 C# 模块的 ASP(活动服务器页面)模块,客户和订单数据访问模块实现为 ADO(ActiveX 数据对象)。
注意事项
分层架构模式是一种可靠的通用模式,对于大多数应用程序来说,它是一个很好的起点,特别是当您不确定哪种架构模式最适合您的应用程序时。但是,从架构的角度来看,选择此模式时需要考虑几件事。
首先要注意的是所谓的架构陷坑反模式。这种反模式描述了请求流经架构的多个层的情况,即简单的传递处理,每层内几乎没有或根本没有执行逻辑。例如,假设表示层响应用户检索客户数据的请求。表示层将请求传递给业务层,业务层只是将请求传递给持久层,然后持久层对数据库层进行简单的 SQL 调用以检索客户数据。然后,数据一路传递回堆栈,无需额外的处理或逻辑来聚合、计算或转换数据。
每个分层架构都会有一些属于架构漏洞反模式的场景。但关键是分析属于此类别的请求的百分比。80-20 规则通常是一个很好的做法,可以遵循该规则来确定您是否正在遇到架构漏洞反模式。通常,大约 20% 的请求是简单的传递处理,而 80% 的请求具有与请求相关的一些业务逻辑。但是,如果您发现此比例被颠倒过来,并且大多数请求都是简单的传递处理,那么您可能需要考虑将一些架构层开放,请记住,由于缺乏层隔离,控制更改将更加困难。
分层架构模式的另一个考虑因素是,即使您将表示层和业务层拆分为单独的可部署单元,它也倾向于构建单片应用程序。虽然这对于某些应用程序来说可能不是问题,但它确实在部署、总体稳健性和可靠性、性能和可扩展性方面带来了一些潜在问题。
模式分析
下表包含分层架构模式的常见架构特征的评级和分析。每个特征的评级基于该特征作为模式典型实现的能力的自然趋势,以及该模式的普遍知名度。要并排比较此模式与本报告中其他模式的关系,请参阅 本报告末尾的模式分析摘要。
- 整体敏捷性
- 评级:低
- 分析:总体敏捷性是指对不断变化的环境做出快速反应的能力。虽然可以通过此模式的隔离层功能隔离变更,但由于大多数实现的单片性质以及此模式中通常存在的组件紧密耦合,因此在此架构模式中进行更改仍然很麻烦且耗时。
- 易于部署
- 评级:低
- 分析:根据您实施此模式的方式,部署可能会成为一个问题,特别是对于较大的应用程序而言。对组件的一个小改动可能需要重新部署整个应用程序(或应用程序的很大一部分),导致需要在非工作时间或周末规划、安排和执行部署。因此,此模式不易适应持续交付流水线,从而进一步降低了部署的总体评分。
- 可测试性
- 评级:高
- 分析:由于组件属于架构中的特定层,因此可以模拟或存根其他层,因此这种模式相对容易测试。开发人员可以模拟表示组件或屏幕以将测试隔离在业务组件内,也可以模拟业务层以测试某些屏幕功能。
- 表现
- 评级:低
- 分析:虽然某些分层架构确实可以表现良好,但由于必须经过架构的多个层才能满足业务请求,因此效率低下,该模式并不适合高性能应用程序。
- 可扩展性
- 评级:低
- 分析:由于该模式的实现趋向于紧密耦合和单片化,因此使用此架构模式构建的应用程序通常难以扩展。您可以通过将各层拆分为单独的物理部署或将整个应用程序复制到多个节点来扩展分层架构,但总体而言,粒度太宽,扩展成本高昂。
- 易于开发
- 评级:高
- 分析:开发简易性得分相对较高,主要是因为这种模式非常知名,而且实现起来并不太复杂。由于大多数公司开发应用程序时会按层(表示、业务、数据库)划分技能集,因此这种模式成为大多数业务应用程序开发的自然选择。公司的沟通和组织结构与其开发软件的方式之间的联系就是所谓的 康威定律。您可以谷歌搜索“康威定律”,了解有关这种迷人相关性的更多信息。
事件驱动架构
事件驱动架构模式是一种流行的分布式异步架构模式,用于生成高度可扩展的应用程序。它还具有高度适应性,可用于小型应用程序以及大型复杂应用程序。事件驱动架构由高度解耦的单一用途事件处理组件组成,这些组件异步接收和处理事件。
事件驱动架构模式由两种主要拓扑组成:中介和代理。当您需要通过中央中介来协调事件中的多个步骤时,通常会使用中介拓扑;而当您想要在不使用中央中介的情况下将事件链接在一起时,则会使用代理拓扑。由于这两种拓扑的架构特征和实施策略不同,因此了解每种拓扑很重要,以便知道哪种拓扑最适合您的特定情况。
中介拓扑
中介拓扑结构对于具有多个步骤且需要一定程度的协调才能处理的事件非常有用。例如,进行股票交易的单个事件可能需要您首先验证交易,然后根据各种合规规则检查该股票交易的合规性,将交易分配给经纪人,计算佣金,最后与该经纪人进行交易。所有这些步骤都需要一定程度的协调来确定步骤的顺序以及哪些步骤可以连续和并行完成。
中介拓扑结构中有四种主要类型的架构组件:事件队列、事件中介、事件通道和事件处理器。事件流从客户端将事件发送到 事件队列开始,事件队列用于将事件传输到事件中介。事件中介 接收初始事件,并通过将其他异步事件发送到事件通道 来执行流程的每个步骤来协调该事件。事件处理器在事件通道上监听,从事件中介接收事件并执行特定的业务逻辑来处理该事件。图 2-1说明了事件驱动架构模式的一般中介拓扑结构。

在事件驱动架构中,通常有十几个到几百个事件队列。该模式未指定事件队列组件的实现;它可以是消息队列、Web 服务端点或它们的任何组合。
此模式中有两种类型的事件:初始事件和处理事件。初始事件是中介接收的原始事件,而处理事件是由中介生成并由事件处理组件接收的事件。
事件中介组件负责协调初始事件中包含的步骤。对于初始事件中的每个步骤,事件中介都会向事件通道发送一个特定的处理事件,然后由事件处理器接收和处理。需要注意的是,事件中介实际上并不执行处理初始事件所需的业务逻辑;相反,它知道处理初始事件所需的步骤。
事件通道由事件中介使用,用于将与初始事件中每个步骤相关的特定处理事件异步传递给事件处理器。事件通道可以是消息队列或消息主题,但消息主题在中介拓扑中使用最为广泛,这样处理事件就可以由多个事件处理器处理(每个处理器根据收到的处理事件执行不同的任务)。
事件处理器组件包含处理处理事件所需的应用程序业务逻辑。事件处理器是独立的、高度解耦的架构组件,可在应用程序或系统中执行特定任务。虽然事件处理器组件的粒度可以从细粒度(例如,计算订单的销售税)到粗粒度(例如,处理保险索赔)不等,但重要的是要记住,一般来说,每个事件处理器组件都应执行单个业务任务,而不是依赖其他事件处理器来完成其特定任务。
事件中介器可以采用多种方式实现。作为架构师,您应该了解每种实现选项,以确保您选择的事件中介器解决方案符合您的需求和要求。
事件中介最简单、最常见的实现是通过开源集成中心,例如 Spring Integration、Apache Camel 或 Mule ESB。这些开源集成中心中的事件流通常通过 Java 代码或 DSL(领域特定语言)实现。对于更复杂的中介和编排,您可以使用 BPEL(业务流程执行语言)与 BPEL 引擎(例如开源 Apache ODE)结合使用。BPEL 是一种标准的 XML 类语言,用于描述处理初始事件所需的数据和步骤。对于需要更复杂编排(包括涉及人机交互的步骤)的大型应用程序,您可以使用业务流程管理器 (BPM)(例如jBPM )实现事件中介。
了解您的需求并将其与正确的事件中介实现相匹配对于使用此拓扑的任何事件驱动架构的成功都至关重要。使用开源集成中心进行非常复杂的业务流程管理编排注定会失败,就像实施 BPM 解决方案来执行简单的路由逻辑一样。
为了说明中介拓扑的工作原理,假设您通过保险公司投保,并且决定搬家。在这种情况下,初始事件可能被称为搬迁事件之类的名称。处理搬迁事件所涉及的步骤 包含在事件中介中,如图 2-2 所示。对于每个初始事件步骤,事件中介都会创建一个处理事件(例如,更改地址、重新计算报价等),将该处理事件发送到事件通道并等待相应的事件处理器(例如,客户流程、报价流程等)处理该处理事件。此过程持续到初始事件中的所有步骤都处理完毕。事件中介中重新计算报价和更新索赔步骤上方的单个横线表示这些步骤可以同时运行。
代理拓扑
代理拓扑与中介拓扑的不同之处在于,它没有中央事件中介;相反,消息流通过轻量级消息代理(例如 ActiveMQ、HornetQ等)以链式方式分布在事件处理器组件中。当您拥有相对简单的事件处理流程并且您不想要(或不需要)中央事件编排时,此拓扑非常有用。
代理拓扑中有两种主要类型的架构组件:代理组件和事件处理器组件。代理组件可以是集中式或联合式的,包含事件流中使用的所有事件通道。代理组件中包含的事件通道可以是消息队列、消息主题或两者的组合。

图 2-3说明了此拓扑。从图中可以看出,没有中央事件中介组件来控制和协调初始事件;相反,每个事件处理器组件负责处理事件并发布新事件来指示它刚刚执行的操作。例如,平衡股票投资组合的事件处理器可能会收到一个名为stock split 的初始事件。基于该初始事件,事件处理器可能会进行一些投资组合重新平衡,然后向代理发布一个名为rebalance portfolio 的新事件,然后该事件将被不同的事件处理器接收。请注意,有时某个事件处理器发布了事件,但其他事件处理器却没有接收该事件。在开发应用程序或提供未来的功能和扩展时,这种情况很常见。

为了说明代理拓扑的工作原理,我们将使用与中介拓扑相同的示例(投保人搬家)。由于在代理拓扑中没有中央事件中介来接收初始事件,因此客户流程组件直接接收事件,更改客户地址,并发出事件说明它更改了客户的地址(例如, 更改地址 事件) 。在此示例中,有两个事件处理器对更改地址事件感兴趣:报价流程和索赔流程。报价处理器组件根据地址更改重新计算新的汽车保险费率,并向系统的其余部分发布事件,指示它做了什么(例如, 重新计算报价事件)。另一方面,索赔处理组件接收相同的更改地址事件,但在这种情况下,它会更新未结的保险索赔,并将事件作为更新索赔事件发布到系统 。然后,这些新事件被其他事件处理器组件拾取,事件链继续通过系统,直到没有针对该特定发起事件发布更多事件为止。

从图 2-4可以看出,代理拓扑就是通过事件链来执行业务功能。理解代理拓扑的最好方法是将其想象成一场接力赛。在接力赛中,运动员手持接力棒跑一段距离,然后将接力棒交给下一位运动员,依此类推,直到最后一名运动员越过终点线。在接力赛中,一旦运动员交出接力棒,比赛就结束了。代理拓扑也是如此:一旦事件处理器交出事件,它就不再参与该特定事件的处理。
注意事项
事件驱动架构模式是一种相对复杂的模式,主要原因是其异步分布式特性。在实现此模式时,您必须解决各种分布式架构问题,例如远程进程可用性、响应能力不足以及代理或中介发生故障时的代理重新连接逻辑。
选择此架构模式时需要考虑的一个问题是单个业务流程缺乏原子事务。由于事件处理器组件高度解耦且分布广泛,因此很难在它们之间维护事务工作单元。因此,在使用此模式设计应用程序时,您必须不断考虑哪些事件可以独立运行,哪些不能独立运行,并相应地规划事件处理器的粒度。如果您发现需要在事件处理器之间拆分单个工作单元(即,如果您对应该是不可分割的事务使用单独的处理器),那么这可能不是适合您的应用程序的模式。
事件驱动架构模式最困难的方面之一可能是事件处理器组件契约的创建、维护和管理。每个事件通常都有一个与之相关的特定契约(例如,传递给事件处理器的数据值和数据格式)。使用此模式时,从一开始就确定标准数据格式(例如 XML、JSON、Java 对象等)并建立契约版本控制策略至关重要。
模式分析
下表包含对事件驱动架构模式的常见架构特征的评级和分析。每个特征的评级基于该特征作为模式典型实现的能力的自然趋势,以及该模式的普遍知名度。要并排比较此模式与本报告中其他模式的关系,请参阅 本报告末尾的模式分析摘要。
- 整体敏捷性
- 评级:高
- 分析:整体敏捷性是指对不断变化的环境做出快速反应的能力。由于事件处理器组件是单一用途的,并且与其他事件处理器组件完全分离,因此更改通常只限于一个或几个事件处理器,并且可以快速进行,而不会影响其他组件。
- 易于部署
- 评级:高
- 分析:总体而言,由于事件处理器组件的解耦特性,这种模式相对容易部署。代理拓扑往往比中介拓扑更容易部署,主要是因为事件中介组件与事件处理器的耦合程度有些紧密:事件处理器组件的更改可能也需要事件中介的更改,因此需要为任何给定的更改部署两者。
- 可测试性
- 评级:低
- 分析:虽然单个单元测试并不太难,但它确实需要某种专门的测试客户端或测试工具来生成事件。这种模式的异步特性也使测试变得复杂。
- 表现
- 评级:高
- 分析:虽然有可能实现一个事件驱动的架构,但由于涉及到所有的消息传递基础设施而导致其性能不佳,但总的来说,该模式通过其异步功能实现了高性能;换句话说,执行解耦的并行异步操作的能力超过了排队和出队消息的成本。
- 可扩展性
- 评级:高
- 分析:此模式通过高度独立和解耦的事件处理器自然实现了可伸缩性。每个事件处理器都可以单独扩展,从而实现细粒度的可伸缩性。
- 易于开发
- 评级:低
- 分析:由于模式的异步特性以及契约创建,以及需要在代码中为无响应的事件处理器和失败的代理提供更高级的错误处理条件,因此开发可能有些复杂。
微内核架构
微内核架构模式(有时称为插件架构模式)是实现基于产品的应用程序的自然模式。基于产品的应用程序是打包并可作为典型的第三方产品以版本形式下载的应用程序。但是,许多公司也开发和发布其内部业务应用程序,如软件产品,并附带版本、发行说明和可插入功能。这些也非常适合此模式。微内核架构模式允许您将其他应用程序功能作为插件添加到核心应用程序中,从而提供可扩展性以及功能分离和隔离。
模式描述
微内核架构模式由两种类型的架构组件组成:核心系统和插件模块。应用程序逻辑被划分在独立的插件模块和基本核心系统之间,从而提供可扩展性、灵活性以及应用程序功能和自定义处理逻辑的隔离。图 3-1 说明了基本的微内核架构模式。
微内核架构模式的核心系统传统上仅包含使系统运行所需的最少功能。许多操作系统都实现了微内核架构模式,因此该模式的名称由此而来。从业务应用程序的角度来看,核心系统通常被定义为通用业务逻辑,不包括针对特殊情况、特殊规则或复杂条件处理的自定义代码。

插件模块是独立的组件,包含专门的处理、附加功能和自定义代码,旨在增强或扩展核心系统以产生额外的业务功能。通常,插件模块应该独立于其他插件模块,但您当然可以设计需要其他插件存在的插件。无论哪种方式,重要的是将插件之间的通信保持在最低限度,以避免依赖性问题。
核心系统需要知道哪些插件模块可用以及如何获取它们。实现这一点的一种常见方法是通过某种插件注册表。此注册表包含有关每个插件模块的信息,包括其名称、数据契约和远程访问协议详细信息(取决于插件如何连接到核心系统)。例如,标记高风险税务审计项目的税务软件插件可能有一个注册表项,其中包含服务名称(AuditChecker)、数据契约(输入数据和输出数据)和契约格式(XML)。如果通过 SOAP 访问插件,它还可能包含 WSDL(Web 服务定义语言)。
插件模块可以通过多种方式连接到核心系统,包括 OSGi(开放服务网关计划)、消息传递、Web 服务,甚至直接点对点绑定(即对象实例化)。您使用的连接类型取决于您正在构建的应用程序类型(小型产品或大型商业应用程序)以及您的特定需求(例如,单一部署或分布式部署)。架构模式本身并未指定任何这些实现细节,只是要求插件模块必须彼此保持独立。
插件模块和核心系统之间的契约可以是标准契约,也可以是自定义契约。自定义契约通常出现在插件组件由第三方开发的情况下,您无法控制插件使用的契约。在这种情况下,通常在插件联系人和标准契约之间创建一个适配器,这样核心系统就不需要为每个插件编写专门的代码。在创建标准契约(通常通过 XML 或 Java Map 实现)时,重要的是要记住从一开始就创建版本控制策略。
模式示例
微内核架构的最佳示例可能是 Eclipse IDE。下载基本的 Eclipse 产品只能为您提供一个花哨的编辑器。但是,一旦您开始添加插件,它就会变成一个高度可定制且有用的产品。互联网浏览器是使用微内核架构的另一个常见产品示例:查看器和其他插件添加了基本浏览器(即核心系统)中没有的额外功能。
对于基于产品的软件来说,例子不胜枚举,但对于大型商业应用程序来说呢?微内核架构也适用于这些情况。为了说明这一点,让我们使用另一个保险公司的例子,但这次涉及保险索赔处理。
索赔处理是一个非常复杂的过程。每个州对保险索赔中允许和不允许的内容都有不同的规则和规定。例如,如果您的挡风玻璃被石头损坏,有些州允许免费更换挡风玻璃,而其他州则不允许。这为标准索赔流程创造了几乎无限的条件。
毫不奇怪,大多数保险索赔应用程序都利用大型且复杂的规则引擎来处理大部分复杂性。然而,这些规则引擎可能会发展成一个复杂的大泥球,其中更改一条规则会影响其他规则,或者进行简单的规则更改需要大量分析师、开发人员和测试人员。使用微内核架构模式可以解决其中许多问题。
图 3-2中看到的文件夹堆栈代表索赔处理的核心系统。它包含保险公司处理索赔所需的基本业务逻辑,但没有任何自定义处理。每个插件模块都包含该州的特定规则。在此示例中,可以使用自定义源代码或单独的规则引擎实例来实现插件模块。无论采用何种实现方式,关键点在于特定于州的规则和处理与核心索赔系统是分开的,可以添加、删除和更改,而对核心系统的其余部分或其他插件模块几乎没有影响。

注意事项
微内核架构模式的一大优点是它可以嵌入或用作另一种架构模式的一部分。例如,如果此模式解决了应用程序特定易变区域中的特定问题,您可能会发现无法使用此模式实现整个架构。在这种情况下,您可以将微服务架构模式嵌入到您正在使用的另一个模式(例如分层架构)中。类似地,上一节关于事件驱动架构中描述的事件处理器组件可以使用微服务架构模式实现。
微服务架构模式为演进式设计和增量开发提供了强大的支持。您可以先构建一个稳固的核心系统,然后随着应用程序的逐步发展,添加特性和功能,而无需对核心系统进行重大更改。
对于基于产品的应用程序,微内核架构模式应始终是您的首选架构,特别是对于那些您将随着时间的推移发布附加功能并希望控制哪些用户获得哪些功能的产品。如果您随着时间的推移发现该模式不能满足您的所有要求,您可以随时将您的应用程序重构为更适合您特定要求的其他架构模式。
模式分析
下表包含微内核架构模式的常见架构特征的评级和分析。每个特征的评级基于该特征作为基于模式的典型实现的能力的自然趋势,以及该模式的普遍知名度。要并排比较此模式与本报告中其他模式的关系,请参阅 本报告末尾的模式分析摘要。
- 整体敏捷性
- 评级:高
- 分析:整体敏捷性是指对不断变化的环境做出快速反应的能力。通过松散耦合的插件模块,可以在很大程度上隔离和快速实施变更。一般来说,大多数微内核架构的核心系统往往很快就会变得稳定,因此相当健壮,并且随着时间的推移几乎不需要进行任何更改。
- 易于部署
- 评级:高
- 分析:根据模式的实现方式,插件模块可以在运行时动态添加到核心系统(例如热部署),从而最大限度地减少部署期间的停机时间。
- 可测试性
- 评级:高
- 分析:插件模块可以单独测试,并且可以很容易地被核心系统模拟,以演示或制作特定功能的原型,而对核心系统几乎不做任何改变。
- 表现
- 评级:高
- 分析:虽然微内核模式本身并不适合高性能应用程序,但一般来说,使用微内核架构模式构建的大多数应用程序性能都很好,因为您可以自定义和简化应用程序以仅包含您需要的功能。 JBoss 应用服务器就是一个很好的例子:借助其插件架构,您可以将应用服务器精简为仅包含您需要的功能,删除昂贵的未使用功能(例如远程访问、消息传递和缓存),这些功能会消耗内存、CPU 和线程并降低应用服务器的速度。
- 可扩展性
- 评级:低
- 分析:由于大多数微内核架构实现都是基于产品的,并且通常规模较小,因此它们以单个单元的形式实现,因此可扩展性不高。根据您实现插件模块的方式,有时可以在插件功能级别提供可扩展性,但总体而言,这种模式并不以生成高度可扩展的应用程序而闻名。
- 易于开发
- 评级:低
- 分析:微内核架构需要深思熟虑的设计和契约管理,因此实施起来相当复杂。契约版本控制、内部插件注册表、插件粒度以及插件连接的广泛选择都增加了实施此模式的复杂性。
微服务架构模式
微服务架构模式正在业界迅速普及,成为单体应用和面向服务架构的可行替代方案。由于这种架构模式仍在不断发展,业界对于这种模式的含义和实现方式存在很多困惑。报告的这一部分将为您提供必要的关键概念和基础知识,以了解这种重要架构模式的优势(和利弊),以及它是否适合您的应用。
模式描述
无论你选择哪种拓扑或实现方式,都有几个适用于一般架构模式的共同核心概念。这些概念中的第一个是单独部署单元的概念。如图 4-1所示,微服务架构的每个组件都作为单独的单元部署,从而可以通过有效且精简的交付管道更轻松地进行部署,提高可扩展性,并在应用程序内实现高度的应用程序和组件解耦。
理解此模式最重要的概念可能是服务组件的概念。与其考虑微服务架构中的服务,不如考虑服务组件,服务组件的粒度可以从单个模块到应用程序的很大一部分不等。服务组件包含一个或多个模块(例如 Java 类),这些模块代表单一用途的功能(例如,为特定城市或城镇提供天气信息)或大型业务应用程序的独立部分(例如,股票交易或确定汽车保险费率)。设计正确级别的服务组件粒度是微服务架构中最大的挑战之一。下面的服务组件编排小节将更详细地讨论这一挑战。

微服务架构模式中的另一个关键概念是它是一种分布式架构,这意味着架构中的所有组件都完全相互分离,并通过某种远程访问协议(例如 JMS、AMQP、REST、SOAP、RMI 等)进行访问。这种架构模式的分布式特性是它实现某些卓越可扩展性和部署特性的方式。
微服务架构的一大亮点是,它源自与其他常见架构模式相关的问题,而不是作为等待问题发生的解决方案而创建的。微服务架构风格自然源自两个主要来源:使用分层架构模式开发的单体应用程序和通过面向服务架构模式开发的分布式应用程序。
从单片应用程序到微服务架构风格的演进路径主要通过持续交付的发展而推动,持续交付是一种从开发到生产的持续部署管道的概念,它简化了应用程序的部署。单片应用程序通常由紧密耦合的组件组成,这些组件是单个可部署单元的一部分,这使得更改、测试和部署应用程序变得繁琐和困难(因此出现了大多数大型 IT 商店中常见的“每月部署”周期)。这些因素通常会导致应用程序脆弱,每次部署新内容时都会崩溃。微服务架构模式通过将应用程序分成多个可部署单元(服务组件)来解决这些问题,这些单元可以独立于其他服务组件进行单独开发、测试和部署。
导致微服务架构模式出现的另一条演进路径来自实施面向服务架构模式 (SOA) 的应用程序所发现的问题。虽然 SOA 模式非常强大,并且提供了无与伦比的抽象级别、异构连接、服务编排以及将业务目标与 IT 功能相结合的承诺,但它仍然很复杂、昂贵、无处不在、难以理解和实施,并且通常对大多数应用程序来说有些过度。微服务架构风格通过简化服务概念、消除编排需求以及简化连接和对服务组件的访问来解决这种复杂性。
模式拓扑
尽管实现微服务架构模式的方法实际上有几十种,但最常见和最流行的三种主要拓扑是: 基于 API REST 的拓扑、基于应用程序 REST 的拓扑和集中式消息传递 拓扑。
基于 REST 的 API拓扑结构适用于通过某种API(应用程序编程接口)公开小型、独立的单个服务的网站。此拓扑结构如图 4-2所示,由非常细粒度的服务组件(因此称为微服务)组成,其中包含一个或两个模块,这些模块独立于其他服务执行特定的业务功能。在此拓扑结构中,这些细粒度的服务组件通常使用通过单独部署的基于 Web 的 API 层实现的基于 REST 的接口来访问。此拓扑结构的示例包括 Yahoo、Google 和 Amazon 发现的一些常见的单一用途基于云的 RESTful Web 服务。

基于 REST 的应用程序拓扑与基于 REST 的 API 方法的不同之处在于,客户端请求是通过传统的基于 Web 或胖客户端的业务应用程序屏幕接收的,而不是通过简单的 API 层。如图4-3所示,应用程序的用户界面层部署为单独的 Web 应用程序,该应用程序通过简单的基于 REST 的接口远程访问单独部署的服务组件(业务功能)。此拓扑中的服务组件与基于 REST 的 API 拓扑中的服务组件不同,这些服务组件往往更大、粒度更粗,并且只占整个业务应用程序的一小部分,而不是细粒度的单一操作服务。这种拓扑通常用于复杂程度相对较低的中小型企业应用程序。

微服务架构模式中的另一种常见方法是集中式消息传递拓扑。此拓扑(如图 4-4所示)类似于先前基于 REST 的应用程序拓扑,不同之处在于此拓扑不使用 REST 进行远程访问,而是使用轻量级集中式消息代理(例如 ActiveMQ、HornetQ 等)。查看此拓扑时,至关重要的是不要将其与面向服务的架构模式混淆或将其视为“SOA-Lite”。此拓扑中的轻量级消息代理不执行任何编排、转换或复杂路由;相反,它只是一种用于访问远程服务组件的轻量级传输。
集中式消息传递拓扑通常用于较大的业务应用程序或需要对用户界面和服务组件之间的传输层进行更复杂控制的应用程序。与前面讨论的基于 REST 的简单拓扑相比,此拓扑的优势在于高级排队机制、异步消息传递、监控、错误处理以及更好的整体负载平衡和可扩展性。通常与集中式代理相关的单点故障和架构瓶颈问题可通过代理群集和代理联合(将单个代理实例拆分为多个代理实例,以根据系统的功能区域划分消息吞吐量负载)来解决。

避免依赖和编排
微服务架构模式的主要挑战之一是确定服务组件的正确粒度级别。如果服务组件的粒度太粗,您可能无法实现此架构模式带来的好处(部署、可伸缩性、可测试性和松散耦合)。但是,粒度太细的服务组件将导致服务编排要求,这将很快将您的精益微服务架构转变为重量级的面向服务架构,并具有基于 SOA 的应用程序通常会具有的所有复杂性、混乱性、费用和无用性。
如果您发现需要在应用程序的用户界面或 API 层内编排服务组件,那么您的服务组件很可能过于细粒度。同样,如果您发现需要在服务组件之间执行服务间通信来处理单个请求,那么您的服务组件很可能过于细粒度,或者从业务功能的角度来看它们没有正确划分。
服务间通信可能会导致组件之间产生不必要的耦合,而这种通信可以通过共享数据库来处理。例如,如果处理互联网订单的服务组件需要客户信息,它可以转到数据库来检索必要的数据,而不是调用客户服务组件内的功能。
共享数据库可以处理信息需求,但共享功能呢?如果服务组件需要包含在另一个服务组件中的功能或所有服务组件共有的功能,有时您可以在服务组件之间复制共享功能(从而违反 DRY 原则:不要重复自己)。这是大多数实现微服务架构模式的业务应用程序中相当常见的做法,以重复小部分业务逻辑的冗余为代价,以保持服务组件独立并分离其部署。小型实用程序类可能属于这种重复代码类别。
如果您发现无论服务组件粒度级别如何,您仍然无法避免服务组件编排,那么这是一个很好的迹象,表明这可能不是适合您的应用程序的架构模式。由于此模式的分布式特性,很难在服务组件之间维护单个事务工作单元。这种做法需要某种事务补偿框架来回滚事务,这给这个相对简单而优雅的架构模式增加了很大的复杂性。
注意事项
微服务架构模式解决了单片应用程序和面向服务架构中存在的许多常见问题。由于主要应用程序组件被拆分为较小的、单独部署的单元,因此使用微服务架构模式构建的应用程序通常更强大、提供更好的可扩展性,并且可以更轻松地支持持续交付。
此模式的另一个优点是它提供了进行实时生产部署的能力,从而大大减少了传统的每月或周末“大爆炸”生产部署的需求。由于更改通常与特定服务组件隔离,因此只需部署更改的服务组件。如果您只有一个服务组件实例,您可以在用户界面应用程序中编写专门的代码来检测活动的热部署并将用户重定向到错误页面或等待页面。或者,您可以在实时部署期间交换服务组件的多个实例,从而在部署周期内实现持续可用性(使用分层架构模式很难做到这一点)。
最后要考虑的一点是,由于微服务架构模式是一种分布式架构,它具有事件驱动架构模式中的一些相同复杂问题,包括合同创建、维护和管理、远程系统可用性以及远程访问身份验证和授权。
模式分析
下表包含对微服务架构模式的常见架构特征的评级和分析。每个特征的评级基于该特征作为模式典型实现的能力的自然趋势,以及该模式的普遍知名度。要并排比较此模式与本报告中其他模式的关系,请参阅 本报告末尾的模式分析摘要。
- 整体敏捷性
- 评级:高
- 分析:整体敏捷性是指对不断变化的环境做出快速反应的能力。由于单元单独部署的概念,变更通常与单个服务组件隔离,从而可以快速轻松地进行部署。此外,使用此模式构建的应用程序往往非常松散耦合,这也有助于促进变更。
- 易于部署
- 评级:高
- 分析:由于远程服务的细粒度和独立性,微服务模式的部署特性非常高。服务通常作为单独的软件单元进行部署,因此可以在白天或晚上的任何时间进行“热部署”。总体部署风险也显著降低,因为失败的部署能够更快地恢复,并且只影响正在部署的服务上的操作,从而使所有其他操作继续运行。
- 可测试性
- 评级:高
- 分析:由于将业务功能分离并隔离到独立的应用程序中,因此可以确定测试范围,从而可以进行更有针对性的测试工作。对特定服务组件进行回归测试比对整个单片应用程序进行回归测试要容易得多,也更可行。此外,由于此模式中的服务组件是松散耦合的,因此从开发角度来看,进行更改而破坏应用程序另一部分的可能性要小得多,从而减轻了因一个小更改而必须测试整个应用程序的测试负担。
- 表现
- 评级:低
- 分析:虽然您可以创建基于此模式实现且性能非常出色的应用程序,但总体而言,由于微服务架构模式的分布式特性,此模式并不自然地适合高性能应用程序。
- 可扩展性
- 评级:高
- 分析:由于应用程序被拆分为单独部署的单元,因此每个服务组件都可以单独扩展,从而可以对应用程序进行微调扩展。例如,股票交易应用程序的管理区域可能不需要扩展,因为该功能的用户量较低,但交易放置服务组件可能需要扩展,因为大多数交易应用程序对此功能的吞吐量要求较高。
- 易于开发
- 评级:高
- 分析:由于功能被隔离到单独的服务组件中,因此开发变得更加容易,因为范围更小且隔离。开发人员在一个服务组件中所做的更改会影响其他服务组件的可能性要小得多,从而减少了开发人员或开发团队之间所需的协调。
太空建筑
大多数基于 Web 的业务应用程序都遵循相同的一般请求流程:来自浏览器的请求到达 Web 服务器,然后到达应用服务器,最后到达数据库服务器。虽然这种模式对于一小部分用户来说效果很好,但随着用户负载的增加,瓶颈开始出现,首先出现在 Web 服务器层,然后出现在应用服务器层,最后出现在数据库服务器层。通常应对基于用户负载增加的瓶颈的方法是扩展 Web 服务器。这相对简单且成本低廉,有时可以解决瓶颈问题。但是,在大多数高用户负载情况下,扩展 Web 服务器层只会将瓶颈转移到应用服务器。扩展应用服务器可能比扩展 Web 服务器更复杂且成本更高,并且通常只会将瓶颈转移到数据库服务器,而数据库服务器的扩展难度更大且成本更高。即使您可以扩展数据库,最终也会得到一个三角形拓扑,三角形的最宽部分是 Web 服务器(最容易扩展),最小的部分是数据库(最难扩展)。
在任何具有极大并发用户负载的大容量应用程序中,数据库通常是您可同时处理多少事务的最终限制因素。虽然各种缓存技术和数据库扩展产品有助于解决这些问题,但事实是,扩展普通应用程序以应对极端负载是一项非常困难的任务。
基于空间的架构模式专门用于解决可扩展性和并发性问题。对于并发用户量可变且不可预测的应用程序,它也是一种有用的架构模式。从架构上解决极端和可变的可扩展性问题通常比尝试扩展数据库或将缓存技术改造成不可扩展的架构更好。
模式描述
基于空间的模式(有时也称为云架构模式)最大限度地减少了限制应用程序扩展的因素。此模式的名称来自元组空间的概念, 即分布式共享内存的概念。通过消除中央数据库约束并改用复制的内存数据网格,可以实现高可扩展性。应用程序数据保存在内存中并在所有活动处理单元之间复制。处理单元可以随着用户负载的增加和减少而动态启动和关闭,从而解决可变的可扩展性问题。由于没有中央数据库,因此消除了数据库瓶颈,从而在应用程序内提供了近乎无限的可扩展性。
符合此模式的大多数应用程序都是标准网站,它们接收来自浏览器的请求并执行某种操作。竞价拍卖网站就是一个很好的例子。该网站通过浏览器请求不断接收来自互联网用户的出价。该应用程序将接收特定项目的出价,用时间戳记录该出价,并更新该项目的最新出价信息,并将信息发送回浏览器。
此架构模式中有两个主要组件:处理单元和虚拟化中间件。图 5-1 说明了基于空间的基本架构模式及其主要架构组件。
处理单元组件包含应用程序组件(或应用程序组件的部分)。这包括基于 Web 的组件以及后端业务逻辑。处理单元的内容因应用程序类型而异 - 较小的基于 Web 的应用程序可能会部署到单个处理单元中,而较大的应用程序可能会根据应用程序的功能区域将应用程序功能拆分为多个处理单元。处理单元通常包含应用程序模块,以及内存数据网格和用于故障转移的可选异步持久存储。它还包含一个复制引擎,虚拟化中间件使用该引擎将一个处理单元所做的数据更改复制到其他活动处理单元。

虚拟化中间件组件负责处理内部事务和通信。它包含控制数据同步和请求处理各个方面的组件。虚拟化中间件包括消息网格、数据网格、处理网格和部署管理器。这些组件将在下一节中详细描述,可以定制编写或作为第三方产品购买。
形态动态
基于空间的架构模式的神奇之处在于虚拟化的中间件组件和每个处理单元中包含的内存数据网格。图 5-2 显示了包含应用程序模块、内存数据网格、用于故障转移的可选异步持久性存储和数据复制引擎的典型处理单元架构。
虚拟化中间件本质上是架构的控制器,负责管理请求、会话、数据复制、分布式请求处理和流程单元部署。虚拟化中间件中有四个主要架构组件:消息网格、数据网格、处理网格和部署管理器。

消息网格
消息网格(如图 5-3所示)管理输入请求和会话信息。当请求进入虚拟化中间件组件时,消息网格组件会确定哪些活动处理组件可用于接收请求,并将请求转发到其中一个处理单元。消息网格的复杂性可以从简单的循环算法到更复杂的下一个可用算法,后者会跟踪哪个处理单元正在处理哪个请求。
数据网格
数据网格组件可能是此模式中最重要和最关键的组件。数据网格与每个处理单元中的数据复制引擎交互,以在发生数据更新时管理处理单元之间的数据复制。由于消息传递网格可以将请求转发到任何可用的处理单元,因此每个处理单元 在其内存数据网格中包含完全相同的数据至关重要。虽然图 5-4显示了处理单元之间的同步数据复制,但实际上这是并行异步完成的,而且 速度非常快,有时只需几微秒(百万分之一秒)即可完成数据同步。


处理网格
处理网格(如图 5-5所示)是虚拟化中间件中的可选组件,当存在多个处理单元时,处理网格负责管理分布式请求处理,每个处理单元负责处理应用程序的一部分。如果收到的请求需要协调不同类型的处理单元(例如,订单处理单元和客户处理单元),则处理网格负责在这两个处理单元之间协调和编排该请求。

部署管理器
部署管理器组件根据负载情况管理处理单元的动态启动和关闭。该组件持续监控响应时间和用户负载,并在负载增加时启动新的处理单元,并在负载减少时关闭处理单元。它是实现应用程序中可变可扩展性需求的关键组件。
注意事项
基于空间的架构模式是一种复杂且昂贵的架构模式。对于负载可变的小型 Web 应用程序(例如社交媒体网站、竞标和拍卖网站),这是一种不错的架构选择。但是,它不太适合具有大量操作数据的传统大型关系数据库应用程序。
虽然基于空间的架构模式不需要集中式数据存储,但通常会包含一个数据存储来执行初始内存数据网格加载并异步保存处理单元所做的数据更新。创建单独的分区以将易失性和广泛使用的事务数据与非活动数据隔离开来也是一种常见做法,以减少每个处理单元内内存数据网格的内存占用。
值得注意的是,虽然这种模式的另一个名称是基于云的架构,但处理单元(以及虚拟化中间件)不必驻留在基于云的托管服务或 PaaS(平台即服务)上。它可以轻松地驻留在本地服务器上,这也是我更喜欢“基于空间的架构”这个名称的原因之一。
从产品实施角度来看,您可以通过第三方产品(如 GemFire、JavaSpaces、GigaSpaces、IBM Object Grid、nCache 和 Oracle Coherence)实施此模式中的许多架构组件。由于此模式的实施在成本和功能(尤其是数据复制时间)方面差异很大,因此作为架构师,您应该先确定您的具体目标和需求,然后再进行任何产品选择。
模式分析
下表包含对基于空间的架构模式的常见架构特征的评级和分析。每个特征的评级基于该特征作为基于模式的典型实现的能力的自然趋势,以及该模式的普遍知名度。要并排比较此模式与本报告中其他模式的关系,请参阅 本报告末尾的模式分析摘要。
- 整体敏捷性
- 评级:高
- 分析:整体敏捷性是指对不断变化的环境做出快速响应的能力。由于处理单元(应用程序的部署实例)可以快速启动和关闭,因此应用程序可以很好地响应与用户负载增加或减少(环境变化)相关的变化。使用此模式创建的架构通常可以很好地响应编码更改,因为应用程序规模较小且模式具有动态特性。
- 易于部署
- 评级:高
- 分析:虽然基于空间的架构一般不是解耦和分布式的,但它们是动态的,并且基于云的复杂工具允许将应用程序轻松地“推送”到服务器,从而简化部署。
- 可测试性
- 评级:低
- 分析:在测试环境中实现非常高的用户负载既昂贵又耗时,这使得测试应用程序的可扩展性方面变得困难。
- 表现
- 评级:高
- 分析:此模式通过内置的内存数据访问和缓存机制实现高性能。
- 可扩展性
- 评级:高
- 分析:高可扩展性源于对集中式数据库的依赖很小或完全不依赖,因此从本质上消除了可扩展性方程中的这个限制瓶颈。
- 易于开发
- 评级:低
- 分析:复杂的缓存和内存数据网格产品使这种模式的开发相对复杂,主要是因为不熟悉用于创建此类架构的工具和产品。此外,在开发这些类型的架构时必须特别小心,以确保源代码中没有任何内容会影响性能和可扩展性。
形态分析总结
图 1-1 总结了本报告中描述的每种架构模式的模式分析评分。此摘要将帮助您确定哪种模式最适合您的情况。例如,如果您的主要架构关注点是可扩展性,您可以查看此图表并发现事件驱动模式、微服务模式和基于空间的模式可能是不错的架构模式选择。同样,如果您为应用程序选择分层架构模式,您可以参考图表查看部署、性能和可扩展性可能是您的架构中的风险领域。

虽然此图表将帮助您选择正确的模式,但在选择架构模式时,还有很多需要考虑的因素。您必须分析环境的各个方面,包括基础架构支持、开发人员技能组合、项目预算、项目截止日期和应用程序大小(仅举几例)。选择正确的架构模式至关重要,因为一旦架构到位,就很难(且成本高昂)地进行更改。