zhaoyu@home:~$

微服务设计模式-服务分解策略

软件的架构可以通俗理解为:一个应用如何拆解为多个组成部分,和这些组成部分之间的关系。

什么是微服务

分层架构

分层架构是一种精典的架构体系,一个系统可以被分为多层,每一层去承担一个责任。分层架构限定了层级之间的依赖,每层必须依赖于它的 直接下层或者间接下层。 有名的三层架构将一个系统分为三层,

  1. 展现层-包含了实现用户接口和外部API的代码。
  2. 业务逻辑层-包含了业务逻辑。
  3. 持久层-实现了和数据库交互的业务逻辑。 分层架构是一个种很好的架构风格,但是也有一些明显的缺点。
  4. 单一的展现层-不能体现出应用被不止一个系统调用。
  5. 单一的持久层-不能体现出应用有可能和多个数据库有交互。
  6. 基于持久层定义业务逻辑层-也就是说,没有数据库,你就不能测试业务逻辑。

六边形架构

六边形架构可以用于替代分层架构。如下图所示,六边形架构将业务逻辑层放到中心位置,相比于封层架构的展现层,六边形架构有一到多个 入口适配器(如REST,web pages等),通过调用业务逻辑来处理从外部进入的请求。应用有也有一到多个外部适配器(DAO等),调用业务 逻辑和外部进行交互。

业务逻辑拥有多个端口(紫色),在java中,一个端口通常是一个接口,这些端口被分为两类:入口端口和出口端口,入口端口时业务逻辑 提供给外部应用调用的,如一个service接口,多个入口适配器可以调用同一个入口端口。出口端口用于调用外部应用,如一个仓库接口, 定义了一系列数据访问操作,用于调用外部数据库。

该架构的一个主要好处是将业务逻辑层通过适配器解耦,业务逻辑不会依赖展现层逻辑和数据访问层逻辑。由于解耦,很容易单独测试业务 逻辑层。

微服务架构

微服务是一种架构风格,它由多个独立的服务组成,每个服务类似于六边形架构。服务之间充分实现解耦,通过REST或异步消息等进行通信。 下面是外卖应用FTGO扩展为微服务后大概的应用架构图:

  • Order Service:管理订单服务。
  • Delivery Service:管理订单的派送
  • Restaurant Service:管理餐厅的信息
  • Kitchen Service:管理订单的准备。
  • Accounting Service:管理账单和支付。

定义一个微服务的架构

定义一个系统的架构不是按部就班的过程,而是一种艺术,需要不断的迭代和创新。下面是一个构件微服务的案例,可以作为一个参考, 大概分为三步,如下图。

  1. 从需求出发,如用户故事,定义系统的操作,系统操作是整个应用必须处理的请求的抽象,是使用抽象领域模型从需求中定义出来的。
  2. 定义系统有哪些服务,分解服务有两种策略,一种是根据业务能力定义,另一种使用领域驱动模型设计子模型划分。
  3. 定义服务API和服务之间的交互协作,定义每个服务的API,将第一步每个系统操作分配给每个服务。一个系统操作可能需要多个service 协同完成。

定义系统操作

定义系统操作需要从需求出发,包括用户案例和对应的用户场景。系统操作的标识分为两步处理。第一步,创建一个由关键类组成的高层次 领域模型。第二步,定义系统操作,并使用领域模型描述每个系统操作的行为。

一个领域模型通过诸如故事和场景名词分析等即使来创建。如产生订单故事:

有一个消费者
    一个餐馆
    一个运送地址/时间。
    一个满足餐馆最低订单标准的订单。
当消费者在餐馆产生一个订单
消费者的账户被授权
    创建等待接单状态下的订单。
    订单关联到消费者
    订单关联到餐厅

该用户场景下,包含的多个类,如:消费者(Consumer),订单(Order),餐厅(Restaurant),和账户(Account)。

类似的,订单接收也可以扩充为如下的场景:

有一个等待接单状态的订单
    和一个可以送单的快递员
当餐馆接单后,并承诺送达时间,
接着,订单被置为已接单状态
    设置订单的送达时间
    将此单分配给快递员

以上场景反应出快递员(Courier)和快递(Delivery)两个类,经过类似的一些回归分析,我们可以得到其他如MenuItem、Address类。 如下类图,显示出主要的类:

上面的类以及各自的职责如下:

  • Consumer:产生订单的用户。
  • Order:由消费者产生,描述订单和订单状态。
  • OrderLineItem:订单中的一条。
  • DeliveryInfo:交付一个订单的地址和时间。
  • Restaurant:餐厅用于准备订单。
  • MenuItem:餐厅菜单上的一项。
  • Courier:快递员。
  • Address:用于记录消费者或者餐馆的地址。
  • Location:快递员的经纬度。

上面我们已经建立起一个领域模型,那么接下来,我们定义应用必须处理的请求。系统 操作可以简单地分为两种:

  • 命令-创建、更新、删除数据相关的系统操作。
  • 查询-读取数据相关的系统操作。

一个系统命令主要从用户故事中获取,主要包含下面几个元素:

每个命令有它的参数、返回值、前置条件和后置条件。如创建订单的操作如下:

如下是接单的系统操作说明:

定义服务

一旦系统操作定义完成,下一步就是定义应用的服务,可以使用不同的分解策略,但是所有的分解策略,分解结果却是相同的,那么就是 一个架构的服务组成,主要是基于业务构建的,而不是基于技术概念。

根据业务能力分解应用

构建微服务的一种策略就是基于业务能力。一个组织的业务能力通过分析组织的目的,结构,还有业务过程来定义,每一个业务能力可以 想象成一个服务。 一个业务能力通常集中在一个特殊的业务对象上,如通知业务,聚焦于通知管理能力上。一个能力一般由多个子能力 组成。 通过分析我们可以得到FTGO外卖系统的业务能力如下:

  • 供给管理
    • 快递员管理-管理快递员信息。
    • 餐馆信息管理-管理餐馆的订单,开放时间,位置等信息。
  • 消费者管理-管理消费者信息。
  • 订单生产和履约
    • 订单管理:消费者创建和管理订单。
    • 餐厅订单管理:一个餐馆内,管理订单的生产。
    • 后勤
    • 可用快递员管理—实时的管理可用的快递员,用以送订单。
    • 运送管理-为消费者运送订单。
  • 账户
    • 消费者账户-管理消费者账单。
    • 餐厅账户-管理餐厅的营收。
    • 快递员账户-管理快递员的营收。

我们定义好了业务能力之后,就需要为每个业务能力或者一组业务能力定义一个服务了。 这种映射的依据根据经验和实际情况而定。如下就是我们将FTGO外卖系统的业务能力定义为对应的服务:

根据业务能力划分服务的一个好处是,由于业务能力一般比较稳定,那么对应的架构也会比较稳定,独立的组件服务可能会根据业务的改 变而变化,但是整个架构不会改变。

根据领域模式分解应用

DDD(Domain-Driven Design)是让复杂的软件应用建立面向对象的领域模型的一种有效方法。DDD有两个概念在微服务架构中非常有用: 子领域和边界上下文。

在传统的模型设计中,可能会对不同的概念使用相同的术语,或者相同的概念又使用了不同的术语,让人困惑。和传统的企业模型不同, DDD为每个子领域定义一个单独的域模型。一个子领域是整个领域的一部分,子领域的划分和划分业务能力的方式一样:分析业务,并 标识出不同区域。子领域划分的结果和业务能力划分的结果非常类似。如下图,FTGO外卖系统划分的结果如下:

DDD将一个领域模型的范围称为边界上下文。一个边界上下文包括了实现领域模型的代码组件。在微服务架构中,一个边界上下文是一个服务 或者一组服务。

分解的一些规则

单一责任原则

每个类只有一个改变它的因素。

要让一个类只有一个改变它的因素,那么一个类只承担一个责任,同时多个类也不能共享一个责任。如:订单类只能负责订单相关的 责任,而商品不能包含订单修改的责任。

共同封闭原则

如果两个类由于共同的原因同时发生改变,那么他们应该属于同一个包,即改变应尽可能地缩小影响的范围,提高应用的可维护性。

分解应用面临的阻碍

  • 网络延迟,分解服务导致了服务之间的网络回路增加,可以通过批处理一次发送多个对象减少这种延迟。
  • 同步通信的可靠性降低。服务之间的调用通过Rest或者RPC,服务出错导致通信失败。
  • 跨服务维护数据一致性,分布式事务等。
  • 数据孤立。
  • God Class阻碍分解。

God Class通常在整个应用中使用,实现了应用多方面的业务逻辑。通常包含多个字段。是领域模型的中心,如:银行中的账户, 电商中的订单等等。由于它将应用多方面的状态和行为绑在一起,所以是分解业务逻辑到服务 中的不可逾越障碍。

FTGO外卖应用中最大的God Class是Order,下图显示了使用传统的模型技术时,Order的机构:

上图中,Order类包含了各个方面的字段和方法。

使用DDD,将每个服务作为一个独立的子领域,并拥有自己的域模型,意思是,FTGO应用中的每个与Order相关的服务都有其自己版本 的关于Order类的域模型。如Dilivery Service,它关于Order的视图如下:

使用DDD改造后的Order如下图:

定义服务API

在划分好服务后,我们需要将系统操作划分给服务,有时候,一个操作的划分不太明显,如;位置更新通知noteUpdatedLocation(), 用于更新快递员的位置。一方面该操作和Courier有关,所以应该划分到Courier Service,另一方面,它也是快递服务,应该划分为 Delivery Service。这种情况下,将它划分到需要操作提供信息的服务合适(Delivery 需要改操作的信息)。在其他情况下,应该划分 到有该操作相关信息的服务(Courier)。

下图是FTGO的一些系统操作的划分:

在划分好系统操作后,下一步需要定义service之间协作需要的API。因为一些操作可能需要跨服务完成交互。

下图是FTGO操作之间需要的跨服务协作。