第2章-分解策略

这章包含:

  • 理解软件架构及其重要性
  • 通过应用按业务能力分解和按子域分解的分解模式, 将应用程序分解为服务
  • 用来自领域驱动设计(DDD)的有界上下文(bounded context)概念来理清数据并使分解更容易

有时候你必须小心你的愿望.经过激烈的游说, Mary 最终说服了业务部门, 迁移到微服务架构是正确的.Mary 感到既兴奋又有些害怕, 她和她的架构师们开了一个长达一上午的会, 讨论从哪里开始.在讨论期间, 很明显, 微服务架构模式语言的某些方面, 如部署和服务发现, 是新的和不熟悉的, 但是很简单.微服务架构的核心挑战是将应用程序分解为服务.因此, 架构的第一个也是最重要的方面是服务的定义.当他们站在白板旁时, FTGO 团队想知道该怎么做!

在本章中, 您将学习如何为应用程序定义微服务架构.我将描述将应用程序分解为服务的策略.您将了解到服务是围绕业务关注点组织的, 而不是围绕技术关注点组织的.我还展示了如何使用来自领域驱动设计(DDD)的思想来消除 god 类, god 类是贯穿整个应用程序使用的类, 它们会导致妨碍分解的复杂依赖关系.

本章首先从软件架构概念的角度定义微服务架构.然后, 我将描述从应用程序的需求开始定义微服务架构的过程.我将讨论将应用程序分解为服务集合的策略、遇到的障碍以及如何克服它们.让我们从审查软件架构的概念开始.

微服务体系结构究竟是什么?

第 1 章描述了微服务架构的关键思想是功能分解.您不是开发一个大型应用程序, 而是将应用程序构造为一组服务.一方面, 将微服务架构描述为一种功能分解是有用的.但另一方面, 它留下了几个未解的问题, 包括微服务架构如何与概念更加广泛的软件架构相关联? 什么是服务? 服务的大小有多重要?

为了回答这些问题, 我们需要后退一步, 看看软件架构是什么意思.软件应用程序的架构是它的高层结构, 它由组成部分和这些部分之间的依赖关系组成.正如您将在本节中看到的, 应用程序的架构是多维的, 因此有多种方法来描述它.架构之所以重要, 是因为它决定了应用程序的软件质量属性或 ilities(可参考 https://zh.wikipedia.org/wiki/非功能性需求).传统上, 架构的目标是可伸缩性、可靠性和安全性.但是今天,架构还支持快速和安全的软件交付, 这一点很重要.您将了解到, 微服务架构是一种架构风格, 它使应用程序具有很高的可维护性、可测试性和可部署性.

我将通过描述软件架构的概念及其重要性来开始这一节.接下来, 我将讨论架构风格的概念.然后, 我将微服务架构定义为一种特殊的架构风格让我们从软件架构的概念开始.

什么是软件架构? 它为什么重要?

架构显然很重要.至少有两个会议专门讨论这个主题: O ‘Reilly 软件架构会议(https://conferences.oreilly.com/softw-architecture)和 SATURN 会议(https://resources.sei.cmu.edu/news-events/events/saturn/).许多开发人员都有成为架构师的目标.但是什么是架构? 它为什么重要?

为了回答这个问题, 我首先定义了术语软件架构的含义.然后, 我将讨论应用程序的架构是多维的以及如何使用视图或蓝图的集合进行最佳描述.然后我描述了软件架构的重要性, 因为它对应用程序的软件质量属性有影响.

软件架构的定义
软件架构有许多定义.例如, 请参阅 https://en.wikiquote.org/wiki/Software_architecture 来阅读其中一些.我最喜欢的定义来自 Len Bass 和他在软件工程研究所(www.sei.cmu.edu)的同事, 他们在将软件架构作为一门学科建立起来方面发挥了关键作用.他们对软件架构的定义如下:

计算系统的软件架构是对系统进行推理所需要的一组结构, 它包括软件元素、它们之间的关系以及两者的属性.
— Bass 等人编写的软件架构文档.

这显然是一个相当抽象的定义.但它的本质是, 应用程序的架构是将其分解为部分(元素)和这些部分之间的关系(关系).分解之所以重要有几个原因:

  • 它促进了劳动和知识的分工.它使具有专业知识的多个人员(或多个团队)能够在一个应用程序上高效地一起工作.
  • 它定义了软件元素如何交互.

分解成各个部分以及这些部分之间的关系决定了应用程序的性能.

软件架构的4+1视图模型

更具体地说, 可以从多个角度查看应用程序的架构, 就像可以从结构、管道、电气和其他角度查看建筑物的体系结构一样.Phillip Krutchen 写了一篇描述软件架构的 4+1 视图模型的经典论文, “架构蓝图-软件架构的 ‘4+1’ 视图模型”(www.cs.ubc.ca/~gregor/teaching/papers/4+1view-architecture.pdf).图 2.1 所示的 4+1 模型定义了软件架构的四个不同视图.它们都描述了架构的一个特定方面, 并由一组特定的软件元素和它们之间的关系组成.

Figure 2.1 The 4+1 view model defines four different views of a software architecture

每个视图的目的如下:

  • 逻辑视图-由开发人员创建的软件元素.在面向对象语言中, 这些元素是类和包.它们之间的关系是类和包之间的关系, 包括继承、关联和依赖.
  • 实现视图-构建系统的输出.该视图由模块和组件组成, 模块表示打包的代码, 组件是由一个或多个模块组成的可执行或可部署单元.在 Java 中, 模块是 JAR 文件,组件通常是 WAR 文件或可执行 JAR 文件.它们之间的关系包括模块之间的依赖关系和组件与模块之间的组合关系.
  • 进程视图-运行时的组件.每个元素都是一个进程, 进程之间的关系表示进程间通信.
  • 部署视图-如何将进程映射到机器上.该视图中的元素由(物理或虚拟)机器和进程组成.机器之间的关系代表了网络.这个视图还描述了进程和机器之间的关系.

除了这四个视图之外, 还有场景-4+1 模型中的 +1-动画视图.每个场景描述特定视图中的各个架构组件如何协作以处理请求.例如, 逻辑视图中的一个场景显示了类如何协作.类似地, 进程视图中的场景显示了进程如何协作.

4+1 视图模型是描述应用程序架构的一种很好的方法.每个视图描述架构的一个重要方面, 场景说明视图的元素如何协作.现在让我们看看为什么架构是重要的.

为什么架构是重要的?

应用程序有两类需求.第一类包括功能需求, 它们定义了应用程序必须做什么.它们通常以用例或用户故事的形式出现.架构与功能需求的关系非常小.您可以用几乎任何架构实现功能需求, 甚至是一个大泥球.

架构非常重要, 因为它使应用程序能够满足第二类需求: 其服务质量需求.这些也被称为质量属性, 即所谓的质量.服务质量要求定义了运行时质量, 如可伸缩性和可靠性.它们还定义了开发周期的质量, 包括可维护性、可测试性和可部署性.您为应用程序选择的架构决定了它如何很好地满足这些质量要求.

架构风格概览

在现实世界中, 建筑物的建筑往往遵循一种特定的风格, 如维多利亚时代、美国工匠或装饰艺术.每一种风格都是一套限制建筑特色和建筑材料的设计决策.架构风格的概念也适用于软件.David Garlan 和 Mary Shaw(软件架构介绍, 1994 年 1 月, https://www.cs.cmu.edu/afs/cs/project/able/ftp/intro_softarch/intro_softarch.pdf)是软件架构学科的先驱, 他们定义了如下的架构风格:

架构风格根据结构组织的模式定义了一系列此类系统.更具体地说, 架构风格决定了在该风格的实例中可以使用的组件和连接器(connectors)的词汇表, 以及关于如何组合它们的一组约束.

特定的架构风格提供了元素(组件)和关系(连接器)的有限调色板, 您可以从中定义应用程序架构的视图.应用程序通常使用架构风格的组合.例如, 在本节后面, 我将描述单体架构是如何将实现视图构造为单个(可执行/可部署)组件的架构风格.微服务架构将应用程序构造为一组散耦合的服务.

分层的架构风格

架构风格的经典例子是分层架构.分层架构将软件元素组织成层.每个层都有一组定义良好的职责.分层架构还限制了层之间的依赖关系.一个层只能依赖于它下面的层(如果严格的分层)或者它下面的任何层.

您可以将分层架构应用于前面讨论的四个视图中的任何一个.流行的三层架构是应用于逻辑视图的分层架构.它将应用程序的类组织成以下层:

  • 表示层-包含实现用户界面或外部 API 的代码
  • 业务逻辑层-包含了业务逻辑
  • 持久层-实现了与数据库交互的逻辑

分层架构是架构风格的一个很好的例子, 但是它也有一些明显的缺点:

  • 单一表示层-它并不表示应用程序可能被多个系统调用这一事实.
  • 单一持久层-它并不表示应用程序可能与多个数据库交互这一事实.
  • 根据持久层定义业务逻辑层-从理论上讲, 这种依赖性阻止您在没有数据库的情况下测试业务逻辑.

而且, 分层架构错误地表示了设计良好的应用程序中的依赖关系.业务逻辑通常定义定义数据访问方法的接口或接口 repository.持久层定义实现 repository 接口的 DAO 类.换句话说, 依赖关系与分层体系结构所描述的相反.

让我们来看一个克服这些缺点的替代架构:六边形架构.

六边形架构风格

六角形架构是分层架构之外的另一种选择.如图 2.2 所示, 六边形架构风格以一种将业务逻辑置于中心的方式组织逻辑视图.与表示层不同, 应用程序有一个或多个入站适配器(inbound adapters), 通过调用业务逻辑处理来自外部的请求.类似地, 应用程序没有数据持久层, 而是有一个或多个出站适配器(outbound adapters), 这些适配器由业务逻辑调用并调用外部应用程序.此架构的一个关键特征和优点是业务逻辑不依赖于适配器.相反, 他们依赖它.

Figure 2.2 An example of a hexagonal architecture

业务逻辑有一个或多个接口.端口(port) 定义了一组操作, 以及业务逻辑如何与外部操作交互.例如, 在 Java 中, 端口通常是 Java 接口。有两种端口: 入站端口和出站端口.入站端口是业务逻辑公开的 API, 它允许外部应用程序调用它.入站端口的一个例子是服务接口, 它定义了服务的公共方法.出站端口是业务逻辑调用外部系统的方式.输出端口的一个例子是存储库接口, 它定义了一组数据访问操作.

围绕业务逻辑的是适配器.与端口一样, 适配器有两种类型: 入站适配器和出站适配器.入站适配器通过调用入站端口处理来自外部的请求.入站适配器的一个例子是 Spring MVC 控制器, 它实现了一组 REST 端点或一组 web 页面.另一个示例是订阅消息的 message broker 客户机.多个入站适配器可以调用相同的入站端口.

出站适配器实现出站端口, 并通过调用外部应用程序或服务处理来自业务逻辑的请求.出站适配器的一个例子是实现访问数据库操作的数据访问对象(DAO)类.另一个例子是调用远程服务的代理类.出站适配器也可以发布事件.

六边形架构风格的一个重要优点是, 它将业务逻辑与适配器中的表示逻辑和数据访问逻辑解耦.业务逻辑不依赖于表示逻辑或数据访问逻辑.由于这种解耦, 独立测试业务逻辑要容易得多.另一个好处是, 它更准确地反映了现代应用程序的架构.业务逻辑可以通过多个适配器调用, 每个适配器实现一个特定的 API 或 UI.业务逻辑还可以调用多个适配器, 每个适配器调用不同的外部系统.六边形架构是描述微服务架构中每个服务的架构的一种很好的方法.

分层结构和六角形架构都是架构风格的例子.它们都定义了架构的构建块, 并对它们之间的关系施加了约束.六角形架构和分层架构以三层架构的形式组织逻辑视图.现在让我们将微服务架构定义为组织实现视图的架构风格.

微服务架构是一种架构风格

我已经讨论了 4+ 1视图模型和架构风格, 因此现在可以定义单体和微服务架构.它们都是架构风格.单体架构是一种架构风格, 它将实现视图结构为单个组件:单个可执行文件或 WAR 文件.这个定义与其他视图无关.例如, 一个单体应用程序可以拥有一个逻辑视图, 该视图按照六角形架构的线条组织.

模式: 单体架构
将应用程序构建为单个可执行/可部署组件.见 http://microservices.io/patterns/monolithic.html.

微服务架构也是一种架构风格.它将实现视图构建为一组多个组件: 可执行文件或 WAR 文件.组件是服务, 连接器是使这些服务能够协作的通信协议.每个服务都有自己的逻辑视图架构, 通常是六边形的架构.图 2.3 显示了 FTGO 应用程序可能的微服务架构.该架构中的服务与业务功能相对应, 例如订单管理和餐厅管理.

模式: 微服务架构
将应用程序构造为松耦合的、独立部署的服务集合, 见 http://microservices.io/patterns/microservices.html.

Figure 2.3 A possible microservice architecture for the FTGO application

在本章的后面, 我将描述什么是业务能力.服务之间的连接器是使用诸如 REST API 和异步消息传递等进程间通信机制实现的.第 3 章更详细地讨论了进程间通信.

微服务架构强加的一个关键约束是服务是松耦合的.因此, 对于服务如何协作有一些限制.为了解释这些限制, 我将尝试定义术语服务, 描述松散耦合意味着什么, 并告诉您为什么这很重要.

什么是服务?

服务是一个独立的、可独立部署的软件组件, 它实现了一些有用的功能.图 2.4 显示了服务的外部视图, 在本例中是 Order Service.服务具有一个 API, 该 API 为其客户端提供对其功能的访问.有两种类型的操作: 命令和查询.API 由命令、查询和事件组成.命令, 比如 createOrder() 执行操作并更新数据.查询, 比如 findOrderById() 检索数据.服务还发布由其客户端消费的事件, 比如 OrderCreated.

Figure 2.4 shows the external view of a service

服务的 API 封装了它的内部实现.与单体不同, 开发人员不能编写绕过 API 的代码.因此, 微服务体系结构强制应用程序的模块化.

微服务架构中的每个服务都有自己的架构, 可能还有技术栈.但是典型的服务具有六角形架构.它的 API 是由与服务的业务逻辑交互的适配器实现的.操作适配器调用业务逻辑, 事件适配器发布业务逻辑发出的事件.

什么是松耦合

微服务架构的一个重要特征是服务是松耦合的(https://en.wikipedia.org/wiki/Loose_coupling), 与服务的所有交互都是通过 API 进行的, API 封装了服务的实现细节.这使得服务的实现可以在不影响其客户端的情况下进行更改.松耦合服务是改进应用程序开发时间周期(包括可维护性和可测试性)的关键.它们更容易理解、更改和测试.

对服务进行松耦合并仅通过 API 进行协作的要求禁止服务通过数据库进行通信.您必须将服务的持久数据视为类的字段并保持它们的私有.保持数据私有使开发人员能够更改其服务的数据库模式, 而不必花费时间与处理其他服务的开发人员进行协调.不共享数据库表还可以改进运行时隔离.例如, 它确保一个服务不能持有阻塞另一个服务的数据库锁.但是, 稍后您将了解不共享数据库的一个缺点是, 维护数据一致性和跨服务查询更加复杂.

共享库的角色

开发人员通常将功能打包在一个库(模块)中, 以便多个应用程序可以重用它, 而无需重复代码.毕竟, 如果没有 Maven 或 npm 存储库, 我们今天会怎样呢? 您可能还想在微服务架构中使用共享库.从表面上看, 这似乎是减少服务中的代码重复的好方法.但是您需要确保不会意外地在服务之间引入耦合.

例如, 假设多个服务需要更新 Order 业务对象.一种方法是将该功能打包为多个服务使用的库.一方面, 使用库可以消除代码重复.另一方面, 考虑当需求影响 Order 业务对象的方式发生变化时会发生什么.您需要同时重新构建和部署这些服务.更好的方法是将可能更改的功能(如订单管理)实现为服务.

您应该努力将库用于不太可能更改的功能.例如, 在一个典型的应用程序中, 每个服务实现一个通用的 Money 类是没有意义的.相反, 您应该创建服务使用的库.

服务规模通常不重要

微服务这个词的一个问题是, 你听到的第一件事就是微小.这意味着服务应该非常小.其他基于大小的术语, 如迷你型服务(miniservice)或 nanoservice 也是如此.实际上, 大小并不是一个有用的度量标准.

更好的目标是将设计良好的服务定义为能够由一个小团队开发的服务, 该团队的开发周期最短, 与其他团队的协作也最少.从理论上讲, 一个团队可能只负责一个服务, 所以服务绝不是微小的.相反, 如果一个服务需要一个大的团队或者需要很长的时间来测试, 那么将团队和服务分开可能是有意义的.或者, 如果您经常因为对其他服务的更改而需要更改某个服务, 或者如果它触发了其他服务中的更改, 则说明它不是松耦合的.您甚至可能构建一个分布式的单体.

微服务架构将应用程序构建为一组小的、松耦合的服务.因此, 它改进了开发时间属性-可维护性、可测试性、可部署性等等-并使组织能够更快地开发更好的软件.它还改进了应用程序的可伸缩性, 尽管这不是主要目标.要为应用程序开发微服务架构, 需要标识服务并确定它们如何协作.我们来看看怎么做.

定义应用程序微服务架构

我们应该如何定义微服务架构? 与任何软件开发工作一样, 起点是编写需求, 希望是领域专家, 也许是现有的应用程序.与许多软件开发一样, 定义架构是一门艺术, 而不是科学.本节描述一个简单的三步流程, 如图 2.5 所示, 用于定义应用程序的架构.但是, 重要的是要记住, 这不是一个你可以机械地遵循的过程.它很可能是迭代的, 并涉及很多创造力.

Figure 2.5 A three-step process for defining an application’s microservice architecture

应用程序用于处理请求, 因此定义其架构的第一步是将应用程序的需求提取到关键请求中.但是, 我没有使用 REST 或消息传递等特定 IPC 技术来描述请求, 而是使用了更抽象的系统操作(system operation)概念.系统操作是应用程序必须处理的请求的抽象.它可以是更新数据的命令, 也可以是检索数据的查询.每个命令的行为都是根据抽象领域模型定义的, 抽象领域模型也是从需求派生出来的.系统操作成为说明服务如何协作的架构场景.

流程中的第二步是确定服务的分解.有几种策略可供选择.一种策略起源于业务架构的规程, 它定义与业务功能相对应的服务.另一种策略是围绕领域驱动的设计子领域组织服务.最终的结果是围绕业务概念而不是技术概念组织的服务.

定义应用程序架构的第三步是确定每个服务的 API.为此, 要将第一步中标识的每个系统操作分配给服务.服务可以完全独立地实现操作.或者, 它可能需要与其他服务协作.在这种情况下, 您将确定服务如何协作, 这通常需要服务来支持其他操作.您还需要决定我在第3 章中描述的实现每个服务的 API 的IPC 机制.

分解有几个障碍.首先是网络延迟.您可能会发现, 由于服务之间的往返(round-trips)太多, 特定的分解是不切实际的.分解的另一个障碍是服务之间的同步通信降低了可用性.您可能需要使用第 3 章中描述的自包含(self-contained)服务的概念.第三个障碍是跨服务维护数据一致性的需求.您通常需要使用在第 4 章中讨论的 sagas.分解的第四个也是最后一个障碍是所谓的 god 类, 它们在整个应用程序中使用.幸运的是, 您可以使用领域驱动设计的概念来消除 god 类.

本节首先描述如何标识应用程序的操作.之后, 我们将研究将应用程序分解为服务的策略和指导方针, 以及分解的障碍和如何解决它们.最后, 我将描述如何定义每个服务的 API.

识别系统操作(system operations)

定义应用程序架构的第一步是定义系统操作.起点是应用程序的需求, 包括用户故事及其关联的用户场景(注意, 这些场景与架构场景不同).使用图 2.6 所示的两步流程标识和定义系统操作.这个过程的灵感来自 Craig Larman 的《应用 UML 和模式》(Prentice Hall, 2004)一书中介绍的面向对象设计过程(参见http://www.craiglarman.com/wiki/index.php?title=Book_Applying_UML_and_Patterns).第一步创建高层域模型, 该模型由提供用于描述系统操作的词汇表的关键类组成.第二步识别系统操作, 并根据域模型描述每个操作的行为.

Figure 2.6 System operations are derived from the application’s requirements using a two-step process

领域模型主要来源于用户描述的名词, 系统操作主要来源于动词.您还可以使用一种称为事件风暴(Event Stroming)的技术来定义域模型, 我在第 5 章中讨论了这种技术.每个系统操作的行为是根据它对一个或多个域对象的影响以及它们之间的关系来描述的.系统操作可以创建、更新或删除域对象, 也可以创建或销毁它们之间的关系.

让我们来看看如何定义一个高级域模型.之后, 我将根据域模型定义系统操作.

创建高级域模型

定义系统操作过程的第一步是为应用程序勾画一个高级域模型.请注意, 这个域模型比最终要实现的要简单得多.应用程序甚至不会有单个域模型, 因为您很快就会了解到, 每个服务都有自己的域模型.尽管进行了极大的简化, 但是高层领域模型在这个阶段仍然很有用, 因为它定义了描述系统操作行为的词汇表.

领域模型是使用标准技术创建的, 例如分析故事和场景中的名词并与领域专家交流.考虑一下, 例如, 下订单(Place Order)故事.我们可以将这个故事扩展到许多用户场景, 包括这个场景:

1
2
3
4
5
6
7
8
9
给定一个消费者
和一个餐厅
以及该餐厅可提供的送货地址/时间
以及满足餐厅最低订购量的总订购量
当顾客为餐馆下订单时
那么消费者的信用卡就被授权了
并且订单是在 PENDING_ACCEPTANCE 状态下创建的
订单与消费者关联
订单与餐厅关联

此用户场景中的名词暗示存在各种类, 包括 ConsumerOrderRestaurantCreditCard.

类似地,接受订单(Accept Order) 的故事可以扩展到如下场景:

1
2
3
4
5
给定处于 PENDING_ACCEPTANCE 状态的订单和可用于配送订单的快递员
当餐馆接受订单时承诺在特定时间内准备好
然后将订单的状态更改为 ACCEPTED
订单的 promiseByTime 被更新为 promised 时间
并且快递员被指定来配送订单

这个场景表明存在 CourierDelivery 类.经过几次迭代分析后的最终结果将是一个域模型, 该模型毫无疑问地由这些类和其他类, 如 MenuItemAddress 组成.图2.7 是显示关键类的类图.

Figure 2.7 The key classes in the FTGO domain model

每个类的职责如下:

  • Consumer-下单的消费者
  • Order-由消费者下的订单.它描述了订单并追踪它的状态.
  • OrderLineItem-订单中的一行条目.
  • DeliveryInfo-配送订单的时间和地点.
  • Restaurant-为顾客准备送货单的餐馆.
  • MenuItem-餐厅菜单上的一个项目.
  • Courier-向消费者配送订单的快递员.它跟踪快递员的可用性和他们当前的位置.
  • Address-消费者或餐馆的地址.
  • Location-快递员的纬度和经度.

类图, 如图 2.7 所示, 说明了应用程序架构的一个方面.但如果没有动画场景, 它也不过是一张漂亮的图片.下一步是定义与架构场景相对应的系统操作.

定义系统操作
定义了高层域模型之后, 下一步是确定应用程序必须处理的请求.UI 的细节超出了本书的范围, 但是您可以想象, 在每个用户场景中, UI 都将请求后端业务逻辑来检索和更新数据.FTGO 主要是一个 web 应用程序, 这意味着大多数请求都是基于 HTTP 的, 但也有可能一些客户端会使用消息传递.因此, 与其使用特定的协议, 不如使用更抽象的系统操作概念来表示请求.

有两种类型的系统操作:

  • 命令(Commands)-创建、更新和删除数据的系统操作
  • 查询(Queries)-读取(查询)数据的系统操作

最终, 这些系统操作将与 REST、RPC 或消息传递端点相对应, 但目前抽象地考虑它们是有用的.让我们首先识别一些命令.

识别系统命令的一个很好的起点是分析用户故事和场景中的动词.例如, 考虑 Place Order 的故事.显然, 系统必须提供一个 Create Order 的操作.许多其他故事单独映射到系统命令.表 2.1 列出了一些关键的系统命令.

表 2.1 FTGO 应用程序的关键系统命令

参与者故事命令描述
消费者创建订单createOrder()创建一个订单
餐厅接受订单acceptOrder()表示餐厅已接受订单, 并承诺在指定时间内准备好
餐厅准备就绪的订单noteOrderReadyForPickup()表明订单已经准备就绪
快递员更新位置noteUpdatedLocation()更新快递员的当前位置
快递员送货上门noteDeliveryPickedUp()表示快递员在将订单送货上门
快递员配送完成noteDeliveryDelivered()表示快递员已送达订单

命令具有一个规范, 该规范根据域模型类定义其参数、返回值和行为.行为规范由调用操作时必须为 true 的前置条件和调用操作后为 true 的后置条件组成.例如, 下面是 createOrder() 系统操作的规范:

系统操作规范
操作createOrder(consumer id, payment method, delivery address, delivery time, restaurant id, order line items)
返回orderId, …
前置条件1. 消费者存在并且可以下单
2. 行项目和餐厅的菜单项对应
3. 配送地址和时间在餐厅的服务范围内
后置条件1. 消费者的信用卡被授权支付订单总额
2. 订单被创建, 状态为 PENDING_ACCEPTANCE

这些先决条件反映了前面描述的 Place Order 用户场景中的 给定(given) 项.后置条件反映了场景中的 然后(then).当调用系统操作时, 它将验证前置条件, 并执行使后置条件为 true 所需的操作.

下面是 acceptOrder() 系统操作的规范:

系统操作规范
操作acceptOrder(restaurantId, orderId, readyByTime)
返回-
前置条件1. 订单状态(order.status)等待接受(PENDING_ACCEPTANCE).
2. 快递员可以配送订单.
后置条件1. 订单状态(order.status)变为 接受了(ACCEPTED).
2. 订单准备时间(order.readyByTime)变为 准备时间(readyByTime).
3. 快递员被分配去配送订单.

它的前置和后置条件反映了前面的用户场景.

大多数与架构相关的系统操作是命令.不过, 有时检索数据的查询也很重要.

除了实现命令之外, 应用程序还必须实现查询.查询为 UI 提供了用户决策所需的信息.在这个阶段, 我们并没有为 FTGO 应用程序设计一个特定的 UI, 但是考虑一下, 例如, 当消费者下订单时的流程:

  1. 用户输入送货地址和时间.
  2. 系统展示可配送的餐馆.
  3. 用户选择餐馆.
  4. 系统展示菜单.
  5. 用户选择商品并付款.
  6. 系统创建订单.

此用户场景建议以下查询:

  • findAvailableRestaurants(deliveryAddress, deliveryTime)-检索能够在指定时间送达指定地址的餐厅.
  • findRestaurantMenu(id)-检索包含菜单项的餐厅信息.

在这两个查询中, findAvailableRestaurants() 可能是架构上最重要的查询.这是一个涉及地理搜索(geosearch)的复杂查询.查询的地理搜索组件包括查找所有的点-餐馆-在一个位置附近-送货地址.它还会过滤掉那些需要准备和取餐时关门的餐厅.此外, 性能非常关键, 因为只要消费者想要下订单, 就会执行此查询.

高层领域模型和系统操作捕获应用程序所做的事情.它们有助于驱动应用程序架构的定义.每个系统操作的行为都用域模型来描述的.每个重要的系统操作代表一个架构上重要的场景, 这是架构描述的一部分.

定义了系统操作之后, 下一步是识别应用程序的服务.正如前面提到的, 没有一个机械化的过程可以遵循.然而, 您可以使用各种分解策略.每一种方法都从不同的角度解决问题, 并使用自己的术语.但是对于所有的策略, 最终的结果是相同的: 由服务组成的架构主要围绕业务而不是技术概念进行组织.

让我们看看第一个策略, 它定义了与业务功能相对应的服务.

通过应用按业务能力分解模式定义服务

创建微服务架构的一种策略是按业务能力分解.业务架构建模中的概念, 业务能力是业务为产生价值所做的事情.给定业务的功能集取决于业务类型.例如, 保险公司的功能通常包括承保、索赔管理、计费、合规等等.在线商店的功能包括订单管理、库存管理、配送等.

模式: 按照业务功能分解
根据业务能力定义服务.见 http://microservices.io/patterns/decomposition/decompose-by-business-capability.html

业务功能定义了组织的工作

组织的业务能力捕捉组织的业务是什么.它们通常是稳定的, 而不是组织如何管理业务, 组织的业务随时间而变化, 有时变化很大.随着越来越多的人使用技术实现许多业务流程的自动化, 这一点在如今尤其明显.例如, 不久前你把支票交给出纳员存入银行.后来, 用 ATM 存入支票成为可能.如今, 你可以用智能手机方便地存入大多数支票.如您所见, 存款支票业务功能一直保持稳定, 但其实现方式已发生了巨大变化.

识别业务功能

通过分析组织的目的、结构和业务流程, 确定组织的业务功能.每个业务功能都可以看作是服务, 除了它是面向业务的, 而不是面向技术的.它的规范由各种组件组成, 包括输入、输出和服务级别协议.例如, 保险承保能力的输入是消费者的申请, 输出包括批准和价格.

业务能力通常关注于特定的业务对象.例如, 索赔业务对象是索赔管理功能的焦点.功能通常可以分解为子功能, 例如, 索赔管理功能有几个子功能, 包括索赔信息管理、索赔审查和索赔支付管理.

不难想象, FTGO 的业务能力包括:

  • 供应商管理
    • 快递员管理-管理快递员信息
    • 餐厅信息管理-管理餐厅的菜单和其他信息, 包括位置和营业时间.
  • 消费者管理-管理消费者的信息
  • 订单接收和履行
    • 订单管理-允许消费者创建和管理订单
    • 餐厅订单管理-管理餐厅订单的准备工作
    • 物流
    • 快递可用性管理-管理快递员对送货单的实时可用性
    • 配送管理-给消费者配送订单
  • 会计核算
    • 消费者会计核算-管理消费者的帐单
    • 餐厅会计核算-管理参订的支付信息
    • 快递员会计核算-管理给快递员的支付信息

顶级功能包括供应商管理、消费者管理、订单接收和履行以及会计核算.可能还会有许多其他顶级功能, 包括与营销相关的功能.大多数顶级功能被分解为子功能.例如, 接受订单和履行被分解为五个子功能.

有趣的是, 这种能力层次结构有三种与餐厅相关的能力: 餐厅信息管理、餐厅订单管理和餐厅会计核算.这是因为它们代表了餐厅运营的三个非常不同的方面.

下面我们将看下如何用业务功能来定义服务.

从业务功能到服务

一旦确定了业务功能, 就可以为每个功能或相关功能组定义服务.图 2.8 显示了 FTGO 应用程序从功能到服务的映射.一些顶级功能, 例如会计核算功能, 被映射到服务.在其他情况下, 子功能映射到服务.

Figure 2.8 Mapping FTGO business capabilities to services

决定将哪个级别的功能层次结构映射到服务, 因为这在某种程度上是主观的.我对这种特定映射的理由如下:

  • 我将供应商管理的子功能映射到两个服务, 因为餐厅和快递员是非常不同类型的供应商.
  • 我将接受订单和履行的功能映射到三个服务, 每个服务负责流程的不同阶段.我将快递员可用性管理和配送管理功能结合在一起, 并将它们映射到单个服务, 因为它们是紧密交织在一起的.
  • 我将会计核算功能映射到它自己的服务, 因为不同类型的会计核算看起来很相似.

稍后, 将(餐馆和快递员的)支付与(消费者的)账单分开可能是有意义的.

围绕功能组织服务的一个关键优点是, 由于它们是稳定的, 因此产生的架构也相对稳定.架构的各个组件可能随着业务方面的变化而演进, 但是架构仍然保持不变.

尽管如此, 重要的是要记住图 2.8 所示的服务仅仅是定义架构的第一次尝试.随着我们对应用领域的了解越来越多, 它们可能会随着时间而发展.特别是, 架构定义过程中的一个重要步骤是研究服务如何在每个关键架构服务中协作.例如, 您可能会发现由于进程间通信过多, 导致关节式分解是低效的, 您必须组合服务.相反, 服务的复杂性可能会增长到值得将其拆分为多个服务的程度.更重要的是, 在 2.2.5 节中, 我描述了分解的几个障碍, 它们可能会导致您重新考虑您的决策.

让我们看看另一种分解基于领域驱动设计的应用程序的方法.

通过应用子域模式分解来定义服务

DDD, 正如 Eric Evans(Addison-Wesley Professional, 2003)在他的优秀著作《领域驱动设计》中描述的那样, 是一种构建以面向对象领域模型开发为中心的复杂软件应用程序的方法.域模式(domain mode) 以可用于解决该域内问题的形式捕获关于该域的知识.它定义了团队使用的词汇表, DDD 称之为 通用语言(Ubiquitous Language).领域模型紧密地反映在应用程序的设计和实现中.在应用微服务架构时, DDD 有两个非常有用的概念: 子域(subdomains)和有界上下文(bounded contexts).

模式: 通过子域分解
根据 DDD 子域定义服务, 详见 http://microservices.io/patterns/decomposition/decompose-by-subdomain.html.

DDD 与传统的企业建模方法有很大的不同, 后者为整个企业创建一个单独的模型.例如, 在这种模型中, 每个业务实体(如客户、订单等)都有一个单独的定义.这种建模的问题在于, 让组织的不同部分同意一个模型是一项艰巨的任务.此外, 它还意味着从组织的给定部分的角度来看, 模型对于他们的需求过于复杂.此外, 领域模型可能令人混淆, 因为组织的不同部分可能对不同的概念使用相同的术语, 也可能对相同的概念使用不同的术语.DDD 通过定义多个领域模型来避免这些问题, 每个模型都具有显式的作用域.

DDD 为每个子域定义了一个单独的领域模型.子域是的一部分, DDD 术语表示应用程序的问题空间.子领域的识别使用与识别业务功能相同的方式: 分析业务并识别不同的专业领域.最终的结果很可能是类似于业务功能的子域.FTGO 中的子域示例包括订单接收、订单管理、厨房管理、配送和财务.正如您所看到的, 这些子域与前面描述的业务功能非常相似.

DDD 将域模型的范围称为有界上下文(bounded context).有界上下文包括实现模型的代码工件.在使用微服务架构时, 每个有界上下文都是一个服务或一组服务.我们可以通过应用 DDD 并为每个子域定义服务来创建微服务架构.图 2.9 显示了子域如何映射到服务, 每个子域都有自己的域模型.

Figure 2.9 From subdomains to services

DDD 和微服务架构几乎完全一致.子域和有界上下文的 DDD 概念很好地映射到微服务架构中的服务.此外, 微服务架构拥有服务的自治团队的概念完全符合 DDD 的概念, 即每个领域模型由单个团队拥有和开发.更好的是, 正如我在本节后面所描述的, 具有自己的域模型的子域的概念是消除 god 类从而使分解更容易的一种很好的方法.

按子域分解和按业务能力分解是定义应用程序微服务架构的两种主要模式.然而, 分解有一些有用的指导原则, 它们的根源在于面向对象的设计.让我们来看一看.

分解指导原则

到目前为止, 我们已经了解了定义微服务架构的主要方法.在应用微服务架构模式时, 我们还可以采用和使用面向对象设计的一些原则.这些原则由 Robert C. Martin 创建的, 并在他的经典著作《使用 Booch 方法设计面向对象的 C++ 应用程序》(Prentice Hall, 1995)中进行了描述.第一个原则是单一职责原则(SRP), 用于定义类的职责.第二个原则是共同封闭原则(CCP), 用于将类组织到包中.让我们看看这些原则, 并看看如何将它们应用于微服务架构.

单一职责原则
软件架构和设计的主要目标之一是确定每个软件元素的职责.单一职责原则如下:

一个类应该只有一个更改的原因.

                                 Robert C. Martin

类所具有的每个职责都是该类更改的潜在原因.如果一个类有多个独立更改的职责, 那么这个类就不会是稳定的.通过遵循SRP, 您可以定义每个类都有一个职责, 因此也就有了进行更改的一个原因.

我们可以在定义微服务体系结构时应用 SRP, 并创建小型、内聚的服务, 每个服务都有一个职责.这将减少服务的规模并增加其稳定性.新的 FTGO 架构是 SRP 运行的一个例子.将食物送到消费者手中的每一个方面-订单接收、订单准备和配送-都是独立服务的责任.

共同封闭原则
另一个有用的原则是共同封闭原则:

另一个有用的原则是共同封闭原则:

包中的类应该针对相同类型的更改聚集在一起.影响包的更改会影响包中的所有类.

                                 Robert C. Martin

其思想是, 如果两个类因为相同的基本原因而同步改变, 那么它们属于同一个包.例如, 这些类可能实现特定业务规则的不同方面.它的目标是, 当业务规则发生更改时, 开发人员只需要更改少量包中的代码(理想情况下只需要更改一个包).坚持 CCP 可以显著提高应用程序的可维护性.

我们可以在创建微服务架构和包组件时应用 CCP, 这些组件出于相同的原因更改为相同的服务.这样做将最小化在某些需求发生更改时需要更改和部署的服务数量.理想情况下, 更改只会影响单个团队和单个服务.CCP 是分布式单体反模式的解毒剂.

SRP 和 CCP 是 Bob Martin 提出的 11 条原则中的 2 条.它们在开发微服务架构时特别有用.其余九条原则用于设计类和包.有关 SRP、CCP 和其他 OOD 原则的更多信息, 请参见 Bob Martin 网站上的文章 “面向对象设计的原则”(http://butunclebob.com/ArticleS.UncleBob.PrinciplesOfOod).

按业务能力和按子域分解以及 SRP 和 CCP 是将应用程序分解为服务的良好技术.为了应用它们并成功地开发微服务架构, 您必须解决一些事务管理和进程间通信问题.

将应用程序分解为服务的障碍

从表面上看, 通过定义与业务功能或子域相对应的服务来创建微服务架构的策略看起来很简单.然而, 你可能会遇到一些障碍:

  • 网络延迟
  • 同步通信降低了可用性
  • 维护跨服务数据一致性
  • 获得数据的一致视图
  • 阻止分解的 God 类

让我们看看每个障碍, 从网络延迟开始.

网络延迟
网络延迟是分布式系统中一直存在的问题.您可能会发现, 对服务的特定分解会导致两个服务之间的大量往返(round-trips).有时, 您可以通过实现一个批处理 API 来在一次往返中获取多个对象, 从而将延迟降低到可接受的程度.但在其他情况下, 解决方案是组合服务, 用语言级别的方法或函数调用替换昂贵的 IPC(进程间通信).

同步通信降低了可用性
另一个问题是如何以不降低可用性的方式实现服务间通信.例如, 实现 createOrder() 操作最简单的方法是让 订单服务 使用 REST 同步调用其他服务, 使用像 REST 这样的协议的缺点是它降低了 订单服务 的可用性.如果其他服务不可用, 它将无法创建订单.有时这是一个值得权衡的问题, 但是在第 3 章中, 您将了解到使用异步消息传递(消除紧密耦合并提高可用性)通常是更好的选择.

维护跨服务数据一致性
另一个挑战是维护跨服务数据一致性.一些系统操作需要更新多个服务中的数据.例如, 当餐馆接收订单时, 厨房服务(Kitchen Service)配送服务(Delivery Service)都必须进行更新.厨房服务改变了票据的状态.配送服务安排了订单的配送.这两个更新都必须以原子方式完成.

传统的解决方案是使用基于提交的两阶段分布式事务管理机制.但是正如您将在第 4 章中看到的, 对于现代应用程序来说, 这不是一个好的选择, 您必须使用一种非常不同的事务管理方法, 这就是 saga.saga 是使用消息传递进行协调的一系列本地事务.Saga 比传统的 ACID 事务更复杂, 但在许多情况下都能很好地工作.Saga 的一个限制是它们最终是一致的.如果您需要以原子方式更新某些数据, 那么它必须驻留在单个服务中, 这可能成为分解的障碍.

获得数据的一致视图
分解的另一个障碍是无法跨多个数据库获得真正一致的数据视图.在单体应用程序中, ACID 事务的属性保证查询将返回数据库的一致视图.相反, 在微服务架构中, 即使每个服务的数据库是一致的, 您也无法获得数据的全局一致视图.如果您需要一些数据的一致视图, 那么它必须驻留在单个服务中, 这可能妨碍分解.幸运的是, 在实践中这很少是一个问题.

阻止分解的 God 类
分解的另一个障碍是所谓的 God 类的存在.God 类是整个应用程序中使用的臃肿类(http://wiki.c2.com/?GodClass).God 类通常为应用程序的许多不同方面实现业务逻辑.它通常有大量字段映射到具有许多列的数据库表.大多数应用程序至少具有这些类中的一个, 每个类代表一个对该领域至关重要的概念: 银行帐户、电子商务订单、保险策略等等.因为 God 类将应用程序的许多不同方面的状态和行为捆绑在一起, 所以要将使用它的任何业务逻辑拆分为服务, 它是不可逾越的障碍.

Order 类是 FTGO 应用程序中 God 类的一个很好的例子.这并不奇怪-毕竟, FTGO 的目的是向客户发送食品订单.系统的很多方面都涉及到订单.如果 FTGO 应用程序只有一个领域模型, 那么 Order 类将是一个非常大的类.它将具有与应用程序的许多不同部分相对应的状态和行为.图 2.10 显示了将使用传统建模技术创建的该类的结构.

Figure 2.10 The Order god class is bloated with numerous responsibilities.

如您所见, Order 类具有与订单处理、餐厅订单管理、配送和支付相对应的字段和方法.这个类还有一个复杂的状态模型, 因为一个模型必须描述来自应用程序不同部分的状态转换.在当前形式下, 该类使得将代码分割为服务变得极其困难.

一种解决方案是将 Order 类打包到一个库中, 并创建一个中央订单数据库.处理订单的所有服务都使用这个库并访问数据库.这种方法的问题在于, 它违反了微服务架构的关键原则之一, 并导致了不受欢迎的紧密耦合.例如, 对 Order schema 的任何更改都需要团队同步更新代码.

另一种解决方案是将 订单 数据库封装在 订单服务 中, 其他服务调用该订单服务来检索和更新订单.这种设计的问题是订单服务将是一个数据服务, 其贫血领域模型包含很少或没有业务逻辑.这两个选项都不吸引人, 但幸运的是, DDD 提供了一个解决方案.

一种更好的方法是应用 DDD, 并将每个服务作为一个单独的子域.并使用其自己的领域模型.这意味着 FTGO 应用程序中与订单相关的每个服务都有自己的领域模型和 Order 类的版本.多领域模型好处的一个很好的例子是配送服务.它对订单的视图, 如图 2.11 所示, 非常简单: 取货地址、取货时间、送货地址和送货时间.而且, 配送服务使用更合适的配送名称, 而不是将其称为订单.

Figure 2.11 The Delivery Service domain model

配送服务对订单的任何其他属性都不感兴趣.

厨房服务(Kitchen Service)也有一个更简单的订单视图.它的 订单 版本称为票据(Ticket).如图 2.12 所示, 票据只是由一个状态、requestedDeliveryTime、一个 prepareByTime 和一个告诉餐馆准备内容的行项目列表组成.它不关心消费者、支付、配送等等.

Figure 2.12 The Kitchen Service domain model

订单 服务具有订单最复杂的视图, 如图 2.13 所示.尽管它有很多字段和方法, 但仍然比原来的版本简单得多.

Figure 2.13 The Order Service domain model

每个领域模型中的 Order 类代表同一个 Order 业务实体的不同方面.FTGO 应用程序必须在不同服务中的这些不同对象之间保持一致性.例如, 一旦订单服务授权了消费者的信用卡, 它就必须在厨房服务中触发票据的创建.类似地, 如果餐厅通过厨房服务拒绝订单, 则必须在订单服务服务中取消订单, 并将客户记入账单服务中.在第 4 章中, 您将学习如何使用前面提到的事件驱动机制 saga 来维护服务之间的一致性.

在创建技术挑战的同时, 拥有多个领域模型也会影响用户体验的实现.应用程序必须在用户体验(即它自己的域模型)和每个服务的域模型之间进行转换.例如, 在 FTGO 应用程序中, 显示给消费者的订单状态来自存储在多个服务中的订单信息.这种转换通常由 API 网关处理, 在第 8 章中讨论.尽管存在这些挑战, 但是在定义微服务体系结构时, 确定和消除 god 类是非常重要的.

现在我们来看看如何定义服务 API.

定义服务 API

到目前为止,我们有一个系统操作列表和一个潜在服务列表.下一步是定义每个服务的 API: 其操作和事件.服务 API 操作存在的原因有两个: 一些操作与系统操作相对应.它们由外部客户端调用, 也可能由其他服务调用.其他操作的存在是为了支持服务之间的协作.这些操作仅由其他服务调用.

服务发布事件主要是为了使其能够与其他服务协作.第 4 章描述了如何使用事件来实现 sagas, 从而维护跨服务的数据一致性.第 7 章讨论了如何使用事件更新支持高效查询的 CQRS 视图.应用程序还可以使用事件通知外部客户端.例如, 它可以使用 WebSockets 向浏览器传递事件.

定义服务 API 的起点是将每个系统操作映射到服务.然后, 我们决定服务是否需要与其他人协作来实现系统操作.如果需要协作, 那么我们将确定这些其他服务必须提供哪些 API 来支持协作.让我们首先看看如何将系统操作分配给服务.

将系统操作分配给服务
第一步是决定哪个服务是请求的初始入口点.许多系统操作整齐地映射到服务, 但有时映射不太明显.例如, 可以考虑noteUpdatedLocation() 操作, 该操作更新快递位置.一方面, 因为它与快递有关, 所以这个操作应该分配给快递服务(Courier service).另一方面, 配送服务(Delivery Service)需要快递位置.在这种情况下, 将操作分配给需要操作提供的信息的服务是一个更好的选择.在其他情况下, 将操作分配给具有处理操作所需信息的服务可能是有意义的.

表 2.2 显示了 FTGO 应用程序中的哪些服务负责哪些操作.

将系统操作映射到 FTGO 应用程序中的服务

服务操作
消费者服务createConsumer()
订单服务createOrder()
餐厅服务findAvailableRestaurants()
厨房服务acceptOrder()
noteOrderReadyForPickup()
配送服务noteUpdatedLocation()
noteDeliveryPickedUp()
noteDeliveryDelivered()

将操作分配给服务之后, 下一步是决定服务如何协作以处理每个系统操作.

确定支持服务之间协作所需的 API
有些系统操作完全由一个服务处理.例如, 在 FTGO 应用程序中, 消费者服务完全独立地处理 createConsumer() 操作.但是其他系统操作跨越多个服务.例如, 处理其中一个请求所需的数据可能分散在多个服务中.例如, 为了实现 createOrder() 操作, 订单服务必须调用以下服务, 以验证其前置条件并使后置条件变为真:

  • 消费者服务-验证消费者是否能够下订单并获得其付款信息.
  • 餐厅服务-确认订单行项目, 确认送货地址/时间在餐厅服务区域内, 验证是否满足订单最低要求, 获取订单行项目价格.
  • 厨房服务-创建票据(Ticket).
  • 会计服务-授权消费者的信用卡。

类似地, 为了实现 acceptOrder() 系统操作, 厨房服务必须调用配送服务来安排快递员配送订单.表 2.3 显示了服务、修改后的 API 和协作者.为了完全定义服务 API, 您需要分析每个系统操作并确定需要什么协作.

表 2.3 服务及其修订的 API 和协作者

服务操作协作者
消费者服务verifyConsumerDetails()-
订单服务createOrder()消费服务 verifyConsumerDetails()
餐厅服务 verifyOrderDetails()
厨房服务 createTicket()
会计服务 authorizeCard()
餐厅服务findAvailableRestaurants()
verifyOrderDetails()
-
厨房服务createTicket()
acceptOrder()
noteOrderReadyForPickup()
配送服务 scheduleDelivery()
配送服务scheduleDelivery()
noteUpdatedLocation()
noteDeliveryPickedUp()
noteDeliveryDelivered()
-
会计服务authorizeCard()-

到目前为止, 我们已经标识了每个服务实现的服务和操作.但重要的是要记住, 我们所描绘的架构是非常抽象的.我们没有选择任何特定的 IPC 技术.此外, 尽管术语operation 暗示了某种基于同步请求/响应的 IPC 机制, 但是您将看到异步消息传递扮演了重要角色.在本书中, 我描述了影响这些服务如何协作的架构和设计概念.

第 3 章描述了特定的 IPC 技术, 包括同步通信机制(如 REST)和使用消息代理的异步消息传递.我将讨论同步通信如何影响可用性,并介绍自包含服务的概念, 该概念不同步调用其他服务.实现自包含服务的一种方法是使用第 7 章介绍的 CQRS 模式.例如, 订单服务可以维护餐厅服务拥有的数据的副本, 以便消除同步调用餐厅服务来验证订单的需要.它通过订阅餐厅服务发布的事件来保持副本的最新更新.

第 4 章介绍了 saga 的概念, 以及如何使用异步消息传递来协调参与该 saga 的服务.除了可靠地更新分散在多个服务中的数据之外, saga 也是实现自包含服务的一种方法.例如, 我描述了如何使用 saga 实现 createOrder() 操作, 该操作使用异步消息传递调用服务, 如消费者服务、厨房服务和会计服务.

第 8 章描述了 API 网关的概念, 它向外部客户端公开 API.API 网关可以使用 API 组合模式(见第 7 章)实现查询操作, 而不是简单地将其路由到服务.API 网关中的逻辑通过调用多个服务并组合结果来收集查询所需的数据.在这种情况下, 系统操作被分配给 API 网关, 而不是服务.服务需要实现 API 网关所需的查询操作.

总结

  • 架构决定了应用程序的灵活性, 包括可维护性、可测试性和可部署性, 它们直接影响开发速度.
  • 微服务架构是一种架构风格, 它使应用程序具有很高的可维护性、可测试性和可部署性.
  • 微服务架构中的服务是围绕业务关注点(业务功能或子领域)组织的, 而不是围绕技术关注点.
  • 有两种模式用于分解:
    • 业务能力分解, 业务能力分解起源于业务架构
    • 基于领域驱动设计的概念, 按子域分解
  • 通过应用 DDD 并为每个服务定义一个单独的领域模型, 您可以消除 god 类, 这些类会导致防止分解的复杂依赖关系.
第1章-逃离单体噩梦
从 fesacr 源码中总结出的 Java 代码编写注意事项