实操微服务设计 - 从需求、领域模型、业务能力到服务

阅读数:11 2020 年 4 月 26 日 13:22

实操微服务设计-从需求、领域模型、业务能力到服务

如何定义一个微服务架构呢?跟所有的软件开发过程一样,一开始我们需要拿到领域专家或者现有应用的需求文档。跟所有的软件开发一样,定义架构也是一项艺术而非技术。本文我们将介绍一种定义应用程序架构的三步式流程,如图 1 所示。世界上并没有一个机械化的流程可以遵循,然后指望这个流程输出一个合理的架构。我们只能介绍一个大概的方法,现实世界中,这是一个不断迭代和持续创新的过程。

实操微服务设计-从需求、领域模型、业务能力到服务

图 1 用于定义应用程序的微服务架构的三步式流程

应用程序是用来处理客户端请求的,因此定义其架构的第一步是将应用程序的需求提炼为各种关键请求。但是,不是根据特定的进程间通信技术(如 REST 或消息)来描述这些请求,而是使用更抽象的系统操作这个概念。系统操作(systemoperation)是应用程序必须处理的请求的一种抽象描述。它既可以是更新数据的命令,也可以是检索数据的查询。每个命令的行为都是根据抽象领域模型定义的,抽象领域模型也是从需求中派生出来的。系统操作是描述服务之间协作方式的架构场景。

该流程的第二步是确定如何分解服务。有几种策略可供选择。一种源于业务架构学派的策略是定义与业务能力相对应的服务。另一种策略是围绕领域驱动设计的子域来分解和设计服务。但这些策略的最终结果都是围绕业务概念而非技术概念分解和设计的服务。

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

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

本节首先介绍如何识别应用程序的系统操作。之后,会研究将应用程序分解为服务的策略和指南、分解的障碍以及如何解决它们。最后,将描述如何定义每个服务的 API。

识别系统操作

定义应用程序架构的第一步是定义系统操作。起点是应用程序的需求,包括用户故事及其相关的用户场景(请注意,这些与架构场景不同)。使用图 2-6 中所示的两步式流程识别和定义系统操作。这个流程的灵感来自 Craig Larman 的名著《Applying UML and Patterns》中介绍的面向对象设计过程。第一步创建由关键类组成的抽象领域模型,这些关键类提供用于描述系统操作的词汇表。第二步确定系统操作,并根据领域模型描述每个系统操作的行为。

实操微服务设计-从需求、领域模型、业务能力到服务

图 2 使用这个两步式流程从应用程序的需求识别系统操作。第一步是创建一个抽象领域模型。第二步是定义系统操作,这些操作是根据领域模型定义的

领域模型主要源自用户故事中提及的名词,系统操作主要来自用户故事中提及的动词。你还可以使用名为事件风暴(Event Storming)的技术定义领域模型,我将在第 5 章中讨论。每个系统操作的行为都是根据它对一个或多个领域对象的影响以及它们之间的关系来描述的。

系统操作可以创建、更新或删除领域对象,以及创建或破坏它们之间的关系。

我们来看看如何定义抽象领域模型。之后,我将根据领域模型定义系统操作。

创建抽象领域模型

定义系统操作的第一步是为这个应用程序描绘一个抽象的领域模型。注意这个模型比我们最终要实现的简单很多。应用程序本身并不需要一个领域模型,因为我们在稍后会学到,每一个服务都有它自己的领域模型。尽管非常简单,抽象的领域模型仍旧有助于在开始阶段提供帮助,因为它定义了描述系统操作行为的一些词语。

创建领域模型会采用一些标准的技术,例如通过与领域专家沟通后,分析用户故事和场景中频繁出现的名词。例如 Place Order 用户故事,我们可以把它分解为多个用户场景,例如这个:

实操微服务设计-从需求、领域模型、业务能力到服务

在这个用户场景中的名词,如 Consumer、Order、Restaurant 和 CreditCard,暗示了这些类都是需要的。
同样,Accept Order 用户故事也可以分解为多个场景,如下:

实操微服务设计-从需求、领域模型、业务能力到服务

这个场景暗示需要 Courier 类和 Delivery 类。在经过几次迭代分析之后,结果显然就是这个领域模型应该包括一些类,如 MenuItem 和 Address 等。图 3 显示了核心类的类图。

实操微服务设计-从需求、领域模型、业务能力到服务

图 3 FTGO 领域模型中的关键类

每一个类的作用如下:

  • Consumer:下订单的用户。
  • Order:用户下的订单,它用来描述订单并跟踪状态。
  • OrderLineItem:Order 中的一个条目。
  • DeliveryInfo:送餐的时间和地址。
  • Restaurant:为用户准备生产订单的餐馆,同时也要发起送货。
  • MenuItem:餐馆菜单上的一个条目。
  • Courier:送餐员负责把订单送到用户手里。可跟踪送餐员的可用性和他们的位置。
  • Address:Consumer 或 Restaurant 的地址。
  • Location:Courier 当前的位置,用经纬度表示。

类似上面这张类图描述了应用程序架构的一个方面。但如果没有对应的场景,这个图也就是仅仅好看而已,并不实用。下一步开始定义对应架构场景的系统操作。

定义系统操作

当定义了抽象的领域模型之后,接下来就要识别系统必须处理的各种请求。我们并不讨论具体的用户界面,但是你能够想象在每一个用户场景下,前端的用户界面向后端的业务逻辑发出请求,后端的业务逻辑进行数据的获取和处理。FTGO 是一个 Web 应用,这意味着它的大部分请求都是基于 HTTP 的。但也有可能一些客户端会使用消息。相比绑定到具体的通信协议,使用抽象的词汇来描述跟系统操作有关的请求更为合理。

有以下两种类型的系统操作。

  • 指令型:创建、更新或删除数据的系统操作。
  • 查询型:查询和读取数据的系统操作。

从根本上说,这些系统操作都会对应到具体的 REST、RPC 或消息端口。但现阶段我们不必在意这些实现细节。让我们先开始识别一些指令。

识别系统指令的切入点是分析用户故事和场景中的动词。例如 Place Order 用户故事,它非常明确地告诉架构师,这个系统必须提供一个 Create Order 操作。很多用户故事都会直接对应或映射为系统命令。表 1 列出了一些关键的系统命令。

实操微服务设计-从需求、领域模型、业务能力到服务

表 1 FTGO 应用程序的重要系统命令

命令规范定义了命令对应的参数、返回值和领域模型类的行为。行为规范中包括前置条件(即当这个操作被调用时必须满足的条件)和后置条件(即这个操作被调用后必须满足的条件)。例如,以下就是 createOrder() 系统操作的规范。

实操微服务设计-从需求、领域模型、业务能力到服务

前置条件对应着 Place Order 用户场景中的 givens,后置条件对应着场景中的 Then。当系统操作被调用时,它会检查前置条件,执行操作来完成和满足后置条件。

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

实操微服务设计-从需求、领域模型、业务能力到服务

前置条件和后置条件对应着之前用户场景中的描述。

多数与系统操作相关的架构元素是命令。查询虽然仅仅是简单地获取数据,但是也同样重要。

应用程序除了实现指令以外,也必须实现查询。查询为用户决策提供了用户界面。在目前阶段,我们并没有开始为 FTGO 应用程序构思任何用户界面,但是需要注意,当消费者下订单时往往是如下所示的过程。

1. 用户输入送餐地址和期望的送餐时间;
2. 系统显示当前可用的餐馆;
3. 用户选择餐馆;
4. 系统显示餐馆的菜单;
5. 用户点餐并结账;
6. 系统创建订单。

这个用户场景包含了以下的查询型操作:
findAvailableRestaurants(deliveryAddress,deliveryTime):获取所有能够送餐到用户地址并满足送餐时间要求的餐馆。
findRestaurantMenu(id):返回餐馆信息和这家餐馆的菜单项。

在这两项查询中,findAvailableRestaurants() 也许是在架构层面尤其重要的一个。它是一个包含了地理位置等信息的复杂查询。地理查询的组件负责找到送餐地址周围所有满足要求的餐馆位置。同时它也需要过滤那些在订单准备和送餐时间范围内没有营业的餐馆。另外,这个查询的性能尤其重要,因为执行这个查询时,客户多数都是“在线急等”的状态,耽误不得。

抽象的领域模型和系统操作能够回答这个应用“做什么”这一问题。这有助于推动应用程序的架构设计。每一个系统操作的行为都通过领域模型的方式来描述。每一个重要的系统操作都对应着架构层面的一个重大场景,是架构中需要详细描述和特别考虑的地方。现在我们来看看如何定义应用程序的微服务架构。

系统操作被定义后,下一步就是完成应用服务的识别。如之前提到的,这并不是一个机械化的流程,相反,有多种拆分策略可供选择。每一种都是从一个侧面来解决问题,并且使用它们独有的一些术语。但是殊途同归,这些策略的结果都是一样的:一个包含若干服务的架构,这样的架构是以业务而不是技术概念为中心。

我们先来看看第一个策略:使用业务能力来定义服务。

2. 根据业务能力进行服务拆分

创建微服务架构的策略之一就是采用业务能力进行服务拆分。业务能力是一个来自于业务架构建模的术语。业务能力是指一些能够为公司(或组织)产生价值的商业活动。特定业务的业务能力取决于这个业务的类型。例如,保险公司业务能力通常包括承保、理赔管理、账务和合规等。在线商店的业务能力包括:订单管理、库存管理和发货,等等。
模式:根据业务能力进行服务拆分

业务能力定义了一个组织的工作

组织的业务能力通常是指这个组织的业务是做什么,它们通常都是稳定的。与之相反,组织采用何种方式来实现它的业务能力,是随着时间不断变化的。这个准则在今天尤其明显,很多新技术在被快速采用,商业流程的自动化程度越来越高。例如,不久之前你还通过把支票交给银行柜员的方式来兑现支票,现在很多 ATM 机都支持直接兑现支票,而今,人们甚至可以使用智能手机拍照的方式来兑现支票。正如你所见,“兑现支票”这个业务能力是稳定不变的,但是这个能力的实现方式正在发生戏剧性的变化。

识别业务能力

一个组织有哪些业务能力,是通过对组织的目标、结构和商业流程的分析得来的。每一个业务能力都可以被认为是一个服务,除非它是面向业务的而非面向技术的。业务能力规范包含多项元素,比如输入和输出、服务等级协议(SLA)。例如,保险承保能力的输入来自客户的应用程序,这个业务能力的输出是完成核保并报价。

业务能力通常集中在特定的业务对象上。例如,理赔业务对象是理赔管理功能的重点。能力通常可以分解为子能力。例如,理赔管理能力具有多个子能力,包括理赔信息管理、理赔审核和理赔付款管理。

把 FTGO 的业务能力逐一列出来似乎也并不太困难,如下所示。

  • 供应商管理。
    Courier management:送餐员相关信息管理;
    Restaurantinformation management:餐馆菜单和其他信息管理,例如营业地址和时间。
  • 消费者管理:消费者有关信息的管理。
  • 订单获取和履行。
    Order management:让消费者可以创建和管理订单。
    Restaurant ordermanagement:让餐馆可以管理订单的生产过程。
    送餐。
    Courieravailability management:管理送餐员的实时状态。
    Deliverymanagement:把订单送到用户手中。
  • 会计记账。
    Consumeraccounting:管理跟消费者相关的会计记账。
    Restaurantaccounting:管理跟餐馆相关的会计记账。
    Courieraccounting:管理跟送餐员相关的会计记账。
    其他。

顶级能力包括供应商管理、消费者管理、订单获取和履行以及会计记账。可能还有许多其他顶级能力,包括与营销相关的能力。大多数顶级能力都会分解为子能力。例如,订单获取和履行被分解为五个子能力。

这个能力层次的有趣方面是有三个餐馆相关的能力:餐馆信息管理、餐馆订单管理和餐馆会计记账。那是因为它们代表了餐馆运营的三个截然不同的方面。

接下来,我们将了解如何使用业务能力来定义服务。

从业务能力到服务

一旦确定了业务能力,就可以为每个能力或相关能力组定义服务。图 4 显示了 FTGO 应用程序从能力到服务的映射。某些顶级能力(如会计记账能力)将映射到服务。在其他情况下,子能力映射到服务。

实操微服务设计-从需求、领域模型、业务能力到服务

图 4  将 FTGO 业务能力映射到服务。能力层次结构各个级别的能力都映射到服务

决定将哪个级别的能力层次结构映射到服务是一个非常主观的判断。我对这种特定映射的理由如下:

  • 我将供应商管理的子能力映射到两种服务,因为餐馆和送餐员是非常不同类型的供应商。
  • 我将订单获取和履行能力映射到三个服务,每个服务负责流程的不同阶段。我将送餐员可用性管理(Courier availability management)和交付管理(Deliverymanagement)能力结合起来,并将它们映射到单个服务,因为它们交织在一起。
  • 我将会计记账能力映射到自己的独立服务,因为不同类型的会计记账看起来很相似。

之后将针对餐馆和送餐员的费用支付和针对消费者的订单收款分开是有意义的。
围绕能力组织服务的一个关键好处是,因为它们是稳定的,所以最终的架构也将相对稳定。架构的各个组件可能会随着业务的具体实现方式的变化而发展,但架构仍保持不变。

话虽如此,重要的是要记住图 4 中显示的服务仅仅是定义架构的第一次尝试。随着我们对应用程序领域的了解越来越多,它们可能会随着时间的推移而变化,特别是架构定义流程中的一个重要步骤是调查服务如何在每个关键架构服务中协作。例如,你可能会发现由于过多的进程间通信而导致特定的分解效率低下,导致你必须把一些服务组合在一起。相反,服务可能会在复杂性方面增长到值得将其拆分为多个服务的程度。此外,在第 5 节中将描述可能导致你重新审视当前分解决策的几个障碍。

本文转载自技术琐话公众号。

原文链接: https://mp.weixin.qq.com/s/taeRlhv_GoC8CTXA9M2XCg

评论

发布