微服务设计之服务拆分

微服务设计方法论

如何定义一个应用的微服务架构,是一个艺术性大于技术性的复杂过程,没有统一的规范。通用的方法是,从需求文档、领域专家或现有应用架构等资料入手,分三个步骤逐步定义出应用的微服务架构:

  1. 定义系统操作(system operations)。系统操作是指应用必须处理的业务请求,分为命令操作(更新数据)和查询操作(获取数据)两种。
  2. 定义服务。系统分解,拆分出每个微服务,可根据业务能力或根据领域驱动设计的子领域两种策略进行服务拆分。
  3. 定义服务API。将系统操作分配给一个或多个服务。一个操作可能由一个服务独自完成,也可能由多个服务协作完成,因此服务API还包括服务协作关系。

定义系统操作

系统操作可以从用户故事及其用户场景中挖掘,包括两个步骤:

  1. 建立由关键类组成的高层次领域模型,领域模型主要来源于用户故事中的名词;
  2. 描述领域模型中每个类的行为,它们就是系统操作,系统操作主要来源于用户故事中的动词。

建立高层次领域模型

建立领域模型有一些标准方法,例如分析用户故事和场景中的名词,和领域专家交流。

以消费者下单为例,可以把这个用户故事扩展成很多用户场景,例如:

顾客下单的一个场景:

Given a consumer
	And a restaurant
	And a delivery address/time that can be served by that restaurant
	And an order total that meets the restaurant's order minimum
When the consumer places an order for the restaurant
Then consumer's credit card is authorized
	And an order is created in the PENDING_ACCEPTANCE state
	And the order is associated with the consumer
	And the order is associated with the restaurant

餐厅接单的一个场景:

Given an order that is in the PENDING_ACCEPTANCE state
	and a courier that is available to deliver the order
When a restaurant accepts an order with a promise to prepare by a particular time
Then the state of the order is changed to ACCEPTED
	And the order's promiseByTime is updated to the promised time
	And the courier is assigned to deliver the order

把里面的名词抽象成类,就构成了高层次的领域模型:
在这里插入图片描述
每个类的职责如下:

  • Consumer—消费者可以下单
  • Order—描述订单及其状态
  • OrderLineItem—订单行
  • DeliveryInfo—配送信息,包括地址和时间等等
  • Restaurant—餐馆可以接单、完成订单并指派外卖员送餐
  • MenuItem—菜单
  • Courier—外卖员负责送餐,维护可用外卖员列表和送餐地址
  • Address—地址信息,消费者地址,餐馆地址
  • Location—位置信息,定位外卖员

分析系统操作

分析系统操作可从用户故事场景中的动词开始挖掘,大部分动词都可以直接映射成命令操作。

例如,上面用户下单的场景,可以挖掘出以下命令操作:

角色故事命令操作描述
消费者下单createOrder()创建订单
餐馆接单acceptOrder()接受订单并提交至准备状态
餐馆准备完成订单noteOrderReadyForPickup()订单准备完成,通知外卖员取餐
外卖员更新位置noteUpdatedLocation()实时更新外卖员位置
外卖员取餐noteDeliveryPickedUp()表示外卖员已经取餐
外卖员送餐noteDeliveryDelivered()表示外卖员已经送餐完成

命令必须根据其涉及的领域模型类规定它的参数、返回值和行为。行为有前置条件和后置条件,必须满足所有前置条件才执行,执行后必须满足所有后置条件才算成功。前置条件对应于用户场景中的 given(给定),后置条件对应于用户场景中的then(然后)。

对于系统操作acceptOrder() ,其规范如下:

操作acceptOrder(restaurantId, orderId, readyByTime)
返回值void
前置条件订单状态为PENDING_ACCEPTANCE,可指派的外卖员列表不为空
后置条件订单状态为ACCEPTED,完成时间为readyByTime,指派一个外卖员送餐

根据业务能力分解服务

业务能力是一个组织为创造价值所做的事情。业务能力定义了一个组织是做什么的,而业务运作方式定义了该组织是如何去做的。业务能力是通常是稳定的,而业务运作方式则可能是多变的。

可以通过分析组织的目的、结构和业务流程来确定组织的业务能力。每个业务能力都可以视为一个服务,注意,这里的业务能力不包括技术能力,这不在业务范畴。

一个业务能力通常专注于特定的业务对象,例如,理赔业务对象是理赔管理功能的重点。一个业务能力通常可以分解为多个子能力, 例如,理赔管理功能具有多个子功能,包括理赔信息管理,理赔审查和理赔支付管理。

一个网上订餐平台,有以下业务能力:

供应商管理:

  • 外卖员管理
  • 餐厅管理

客户管理:

  • 消费者管理

核心业务:

  • 订单管理 — 消费者创建和管理订单
  • 餐厅订单管理 — 商家管理订单
  • 物流管理
  • 配送管理

财务结算:

  • 消费者结算
  • 餐厅结算
  • 外卖员结算

将业务能力映射为服务

可以将一个或多个相关的业务能力映射为一个服务。具体的粒度并没有统一的准则,取决于业务复杂度,可以将多个密切相关的简单业务合并为一个服务,也可以将一个业务细分成很多子业务,每个子业务对应一个服务。

在这里插入图片描述

根据领域驱动设计分解服务

领域驱动设计( Domain-driven design)是一种综合系统分析和系统设计的面向对象建模方法,它将以前割裂的系统分析和系统设计过程统一起来,使得系统业务到系统架构实现是一致的,并通过所谓的通用语言(Ubiquitous Language)来描述。

领域驱动设计的核心是建立面向对象的领域模型(domain model),它包括了解决该领域内问题的所有知识(knowledge)。知识既包括涉及哪些对象,还包括这些对象的行为,这样才能解决问题。因此,相较于传统的将业务对象和业务行为分离的设计方法,领域驱动设计方法更加丰满,避免了从分析到设计的偏离。

领域驱动设计中有两个对微服务架构设计非常有用的概念:子域(subdomains)和限界上下文(bounded contexts)

传统的企业建模方法为整个企业创建单个模型。在这样的模型中,每个业务实体(如客户,订单)只有一个单独的实体定义。这种建模方法有以下不足:

  • 不同部门难以对这个单一实体建模达成共识;
  • 对单个部门而言,该实体过于复杂;
  • 易造成混乱,沟通成本大,同一术语描述不同的概念,不同术语描述同一概念。

领域驱动设计为每个子域定义一个单独的域模型。域是应用的问题空间,即如何描述和实现企业的业务能力;子域是域的一部分,即如何描述和实现一个特定的业务能力。因此,子域划分与业务能力划分基本上是一致的。
在这里插入图片描述
领域驱动设计将域模型的范围称为限界上下文,限界上下文包括实现域模型的代码构件( code artifacts)。使用微服务架构时,限界上下文是一个或一组服务。我们可以通过领域驱动设计为每个子域定义一个服务,从而构建微服务架构。

领域驱动设计可以和微服务架构完美结合,至少在以下方面,两者完美一致:

  • 子域和限界上下文的概念正好对应微服务架构中的每个服务;
  • 微服务架构强调自治的服务团队,领域驱动设计中每个域模型由单个团队负责开发和维护;
  • 具有独立域模型的子域天然具有消除主类(god class)的能力,这有利于微服务划分。

服务分解原则

无论使用哪种策略,分解微服务的时候,都应遵循一些基本的面向对象设计原则。其中最重要的两个原则是单一职责原则(Single Responsibility Principle)和共同闭包原则(Common Closure Principle),前者用于类设计,后者用于包设计。

A class should have only one reason to change.

类的每个职责都有可能引起类的改变,如果一个类有多个独立改变的职责,那么该类无法保持稳定。将单一职责原则应用于微服务设计,需要保证一个服务只负责一种业务能力或一个子域,这样可以减小服务粒度,增加服务的稳定性。

The classes in a package should be closed together against the same kinds of changes. A change that affects a package affects all the classes in that package.

共同闭包原则要求把因为某个同样的原因而需要修改的所有类组合进一个包里。如果两个类从物理上或者从概念上联系得非常紧密,它们通常一起发生改变,那么它们应该属于同一个包,例如,它们可能实现了同一业务逻辑的不同方面。这样做的好处是,如果业务逻辑变化,只需要修改很少的包(理想情况是一个包)。在微服务架构设计中,应将会因某个原因一起改变的组件打包成一个服务,理想的情况是,一个改变只影响一个团队或一个服务。

服务分解的阻碍

服务分解后,应用由大量分布式部署的服务组成。分布式的复杂性反过来成了服务分解的阻力,是决定服务粒度的一个重要因素。这些阻碍包括:

  • 网络延迟
  • 同步通信造成的低可用性
  • 保持服务间的数据一致性
  • 获得一致的数据视图
  • 主类(God classes)影响分解

这里的主类是指整个应用都要用到的类,这种类往往实现了业务逻辑的多个方面,从而变得非常臃肿,通常是映射到具有很多字段的数据表的持久化类。很多应用都至少有一个这样的类,代表着应用的核心业务逻辑,例如银行应用的账户类,电商应用的订单类等等。这种类把应用不同方面的状态和行为绑定在一起,给分解带来巨大的困难。这也是领域驱动设计适合微服务的一个重要原因,因为领域驱动设计的子域概念,可以消灭主类。

定义服务API

服务的API即服务的操作和事件,它们来自两部分:分配到本服务的系统操作和服务间协作时的通信接口。

有些系统操作可以很直观地分配给某个服务,有些则不是很明显。例如更新外卖员位置的noteUpdatedLocation()操作,外卖员服务和配送服务都需要用到外卖员位置信息,该分配给哪个服务?在这个例子中,分配给操作使用方会更合理,也就是配送服务。有时候,分配给操作实施方会更合理,视情况而定。
在这里插入图片描述
有些系统操作可能需要多个服务共同协作完成,例如 createOrder(),订单服务必须调用多个服务来验证前置条件和后置条件:

  • 消费者服务,验证客户可以下单并获取支付信息
  • 餐厅服务,验证菜单、验证送货地址和时间、验证最低消费并获取订单向价格
  • 厨房服务,开小票
  • 结算服务,授权客户信用卡或交易账户

因此,要完全定义服务的API,必须分析每个服务的系统操作和涉及的服务间协作操作。
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值