前言:
企业应用程序的核心是业务逻辑,业务逻辑实现了业务规则。但是由于业务逻辑分散在多个服务上,因此在微服务架构中开发复杂的业务逻辑更具有挑战性。
我们需要解决两个问题:
- 首先,典型的领域模型是由各种类交织在一起的一个网络。虽然这在单体应用程序中不是问题,但是在微服务架构中,类分散在不同的服务中,就需要跨越服务边界(也就是进程)的对象应用
- 其次,是在设计微服务架构下的业务逻辑,这些业务逻辑受到微服务下事务管理的种种约束。我们的业务逻辑可以在一个服务内部使用ACID事务,它必须使用Saga模式来维护服务之间的数据一致性
好在,我们可以使用领域驱动设计中的聚合模式来解决这些问题。聚合模式下,服务的业务逻辑通过多个聚合组成的一个集合来体现。聚合是一组对象,可以作为一个单元来处理。在微服务架构中开发业务逻辑时,聚合可以起到以下两个重要的作用:
- 使用聚合可以避免任何跨服务边界的对象引用,因为聚合之间通过主键进行引用,而不是通过对象的地址进行引用
- 由于单个事务只能创建或更新单个聚合,因此聚合满足微服务事务模型的约束
业务逻辑组织模式
- 业务逻辑是六边形架构的核心。业务逻辑的周围是入站和出站适配器。入站适配器处理来自客户端的请求并调用业务逻辑。出站适配器被业务逻辑调用,然后它们再调用其他服务和外部应用程序。图5-1显示了一个典型的服务架构
例如 go-micro微服务框架流程:
- proto/.micro.go:文件是入站适配器,接收来自客户端的rpc请求,找到请求对象的方法,调用业务逻辑(Handler)
- Handler:就是业务逻辑目录,在handler中放置业务逻辑类,业务逻辑根据入站适配器传入的出站适配器结构体参数,根据出站适配器,返回数据,调用出站适配器,完成业务逻辑的处理
- proto/.pd.go:文件是出站适配器,定义了返回给客户端的参数,以及出站的业务逻辑。
- 业务逻辑通常是服务中最复杂的部分。在开发业务逻辑时必须做出的关键决策是选用面向对象,还是面向过程。组织业务逻辑有两种模式:面向过程的事务脚本模式和面向对象的领域建模模式。
- models:即是我们自己定义扩展的Databases 数据模型
使用事务脚本模式设计业务逻辑
- 虽然我们一直提倡使用面向对象的方式,但是在某些情况下使用面向对象的方式会有一种“杀鸡用刀到的感觉”,例如在开发简单的业务逻辑的时候。这种情况下,更好的方法是使用面向过程的方式
- 模式:事务脚本
将业务逻辑组织为面向过程的事务脚本集合,每种类型的请求都有一个脚本。
使用领域模型模式设计业务逻辑
- 模式:领域模型
将业务逻辑组织为具有状态和行为的类构成的对象模型
关于领域驱动设计
领域驱动设计(DDD)是对面向对象设计的改进,是开发复杂业务逻辑的一种方法。领域驱动设计的子域概念有助于把应用程序分解为服务。使用DDD时,每个服务都有自己的领域模型,这就避免了在单个应用程序全局范围内的领域模型问题。子域和相关联的界限上下文的相关概念是两种战略性DDD模式
开发人员广泛采用的基本元素包括以下几种:
- 实体:具有持久化ID的对象。具有相同属性值的两个实体仍然是不同的对象
- 值对象:作为值集合的对象。具有相同属性值的两个值对象可以互换使用。值对象的一个例子是Money类,它由币种和金额组成
- 工厂:负责实现对象创建逻辑的对象或方法该逻辑过于复杂,无法由类的构造函数直接完成。它还可以隐藏被实例化的具体类。工厂方法一般可实现为类的静态方法
- 服务:实现不属于实体或值对象的业务逻辑的对象
使用聚合模式设计领域模型
-
聚合拥有明确的边界
聚合是一个边界内的领域对象的集群,可以将其视为一个单元。它由一个根实体和可能的一个或多个其他实体和值对象组成。许多业务对象都被建模为聚合 -
模式:聚合
将领域模型组织为聚合的集合,每个聚合都是可以作为一个单元进行处理的一组对象构成的图。
聚合将领域模型分解为块,单独的每一块更容易理解。它们还阐明了加载、更新和删除等操作的范围。这些操作作用于整个聚合而不是部分聚合。聚合通常从数据库中完整加载,从而避免了延迟加载所导致的任何复杂性。删除聚合会从数据库中删除其所有对象。 -
聚合代表了一致的边界
更新整个聚合而不是聚合的一部分,可以解决一致性的问题。在聚合根上调用更新操作,这会强制执行各种不变量约束。此外,可以使用例如,版本号或数据库级锁锁定聚合根来处理并发性。例如,客户端必须在Order聚合的根上调用方法,而不是直接更新订单项的数量,这会强制执行订单的一些规则约束。 -
识别聚合是关键
在领域驱动设计中,设计领域模型的关键部分是识别聚合,以及它们的边界和根。聚合内部结构的细节是次要的。然而,聚合的价值不仅仅是帮助我们设计模块化的领域模型。更重要的是聚合必须遵守某些规则 -
聚合的规则
领域驱动设计要求聚合遵守一组规则。这些规则确保聚合是一个可以强制执行各种不变量约束的自包含单元
规则一:只引用聚合根
它要求聚合根是聚合中唯一可以由外部类引用的部分。客户端只能通过调用聚合根上的方法来更新聚合。例如,服务使用存储库从数据库加载聚合并获取对聚合根的引用。它通过在聚合根上调用方法来更新聚合。此规则可确保聚合能够强制执行各种不变量约束。规则二:聚合之间的引用必须使用主键
另一个规则是引用聚合必须通过标识(列如,主键)而不是对象引用规则三:在一个事务中,只能创建或更新一个聚合
聚合必须遵守的另一个规则是一个事务只能创建或更新一个聚合。在开发单体应用时,因为有数据库的事务可以更新多个聚合,所以用处不大,但是在微服务架构中,这个约束是完美的。它可以确保单个事务的范围不超越服务的边界。这个规则让创建或更新多个聚合的操作变得更加复杂。但这是正式Saga旨在解决的问题。Saga的每一步都只创建或更新一个聚合
-
聚合的颗粒度:
在开发领域模型时,我们必须做出的关键决策是决定每个聚合的大小。一方面,聚合理想上应该很小。由于每个聚合的更新都是序列化的,因此更细粒度的聚合将提高应用程序能同时处理的请求数量,从而提高可扩展性。它还将改善用户体验,因为它降低了两个用户尝试同时更新一个聚合而引发冲突的可能性。但是,另一方面,因为聚合事务的范围,所以我们可能需要定义更大聚合以使特定的聚合更新操作满足事务的原子性。 -
使用聚合设计业务逻辑:
在典型的微服务中,大部分业务逻辑由聚合组成。其余业务逻辑存在于领域服务和Saga中。Saga编排本地事务的序列,以确保数据的一致性。服务是业务逻辑的入口,由入站适配器调用。服务使用存储库从数据库中检索聚合或聚合保存到数据库。每个存储库都由数据库的出站适配器实现
总结:
- 事务脚本模式通常是实现简单业务逻辑的好方法。但是在实现复杂的业务逻辑时,应该考虑使用面向对象的领域模型模式
- 设计服务的业务逻辑的好方法是使用DDD聚合。DDD聚合很有用,因为它们把领域模型模块化,消除了服务之间对象的直接引用,并确保每个ACID事务都在服务内
- 创建或更新聚合时应发布领域事件。领域事件具有广泛的用途。领域事件的订阅者还可以通知用户和其他应用程序,并将WebSocket消息发布到用户的浏览器。