聚合模式
解决两个问题
1.避免跨越服务边界(也就是进程)的对象引用
2.业务逻辑受到微服务管理下事务管理的种种约束
聚合可以起到两个重要的作用:
1.使用聚合可以避免任何跨服务边界的对象引用,因为聚合之间通过主键进行引用,而不是通过对象的地址进行引用
2.由于单个事务只能创建或更新单个聚合,因此聚合满足微服务事务模型的约束
两种设计风格
事务脚本模型
特征:实现行为的类和存储状态的类是分开的额
将业务逻辑组织为事务脚本。在典型的基于事务脚本的设计中,一组类实现行为,另一组类负责存储状态。事务脚本通常被写成没有状态的类。脚本访问没有行为的数据类以完成持久化任务。
此种设计风格高度面向过程
适用于简单业务逻辑
优点:可以快速简单地开发
缺点:随着业务逻辑变得复杂,代码难以维护
领域模型
领域模型以解决具体问题的方式包含了一个领域内的知识。它定义了当前领域相关团队的词汇表,DDD也称之为通用语言
将业务逻辑组织为由具有状态和行为的类构成的对象类型
将业务逻辑组织为领域模型。大多数业务逻辑由具有状态和行为的类组成
优点:
1.易于理解和维护。不是由一个完成所有事的大类组成,而是由许多小类组成,每个小类都有少量的职责
2.面向对象设计更容易测试:每个类都可以并且应该能够被独立测试
3.最后面向个对象设计更容易扩展
开发人员广泛采用的基本元素
1.实体:具有持久化ID的对象。具有相同属性值的两个实体仍然是不同的对象。在Java EE应用程序中,使用JPA@Entity进行持久化的类通常就是DDD实体。
2.值对象:作为值集合的对象。具有相同属性值的两个值对象可以互换使用。值对象的一个例子是Money类,它由币种和金额组成。
3.工厂:负责实现对象逻辑创建的方法或对象,该逻辑过于复杂,无法由类的构造函数直接完成。它可以隐藏被实例化的具体类。工厂方法一般可以实现为类的静态方法。
4.存储库:用来访问持久化实体的对象,存储库也封装了访问数据库的底层机制
5.服务:实现不属于实体或值对象的业务逻辑的对象。
使用聚合模式设计DDD模型
聚合有明确的边界
聚合是是一个边界内的领域对象的集群,可以将其视为一个单元。它由根实体和可能的一个或多个其他实体和值对象组成
聚合将领域模型分解为块,单独的每一块更容易理解。它们还阐明了加载、更新和删除等操作的范围。这些操作作用于整个聚合而不是部分聚合。聚合通常从数据库中完整加载,从而避免了延迟加载所导致的任何复杂性。删除聚合会从数据库中删除其所有对象
聚合代表了一致的边界
识别聚合是关键
在领域驱动设计中,设计领域模型的关键部分是识别聚合,以及它们的边界和根。
聚合的规则
1.只引用聚合根
2.聚合之间的引用必须使用主键
3.在一个事务中,只能创建或更新一个聚合
聚合的颗粒度
聚合的粒度应该尽可能细化
领域事件
在领域驱动设计的上下文中,领域事件是聚合发生的事情。它由领域模型的一个类表示。事件通常代表状态的变化。
聚合在被创建时,或发生其他重大更改时发布领域事件。
事件增强
识别领域事件
事件风暴
事件风暴的结果是一个以事件为中心的领域模型,它由聚合和事件组成。
事件风暴主要包括三个步骤:
1.头脑风暴
2.识别事件触发器
3.识别聚合
生成和发布领域事件
生成领域事件
发布领域事件
消费领域事件
领域设计模式与传统单体应用程序的区别与共性
1.在许多方面,基于微服务的应用程序的业务逻辑与单体应用程序的业务逻辑没有什么不同。它由诸如服务、JPA支持的实体和存储库等这样的类构成。但是还有一些不同之处。领域模型被设被组织成一组DDD聚合,在其上可以施加各种约束。与传统的对象模型不同,不同聚合中的类之间的引用是基于主键而不是对象引用。此外,事务只能创建或者更新单个聚合。聚合在状态发生变化时会发布领域事件。
2.另一个主要的区别是服务通常使用Saga来维护多个服务之间的数据一致性。
DDD(Domain Driven Design)
DDD即领域驱动设计,是构建复杂软件的方法论,这些软件通常都以面向对象和领域模型为核心
领域
领域是DDD中用来描述应用程序问题域的一个术语
子域
子域是领域的一部分。识别子域的方式与识别业务能力一样:分析业务并识别业务业务的不同专业领域,分析产出的子域定义结果也会跟业务能力十分相近。
限界上下文
DDD把领域模型的边界称为限界上下文(bounded context)
CAP(Consistency, Availability, Partition tolerance)
分布式系统的三个指标
Saga
一种消息驱动的本地事务序列,其是基于松耦合、异步服务概念构建的。由一连串本地事务组成。每个本地事务负责更新它所在服务的私有数据库,并使用异步消息实现Saga步骤间的协调。当Saga的步骤因违反业务规则而失败时,Saga必须通过执行补偿事务显示撤销先前步骤所做的更新。Saga按正常事务的反向顺序来执行补偿事务。
Saga的协调逻辑实现方法一共有两种:
1.协同式:把Saga的决策和执行顺序逻辑分布在每一个Saga参与方中,它们通过交换事件的方式来进行沟通。
2.编排式:把Saga的决策和执行顺序逻辑集中在一个Saga编排器类中。Saga编排器类发出命令式消息给各个参与方,指示这些参与方完成具体操作(本地事务)。状态机是建模Saga编排器的一个好方法(更常用)
编排式Saga的好处和弊端:
好处:
1.更简单的依赖关系
2.较少的耦合
3.改善关注点隔离,简化业务逻辑
弊端:
1.在编排器中集中过多业务逻辑的风险(可通过设计只排序的编排器来避免此类问题)
解决隔离问题
Saga的事务模型是ACD,缺乏隔离性,会导致异常。处理缺乏隔离的Saga策略称为对策。
缺乏隔离性带来的三个问题:
1.丢失更新:一个Saga没有读取更新,而是直接覆盖了另一个Saga所做的更改2
2.脏读:一个事务或者一个Saga读取了尚未完成的Saga所做的更新
3.模糊或者不可重复读:一个Saga的两个不同步骤读取相同的数据却获得了不同的结果,因为另一个Saga已经进行了更新。
解决方法:
1.语义锁定对策:应用程序级别的琐
2.交换式更新:把更新操作设计成可以按任何顺序执行
3.悲观视图:重新排序Saga的步骤,以最大限度降低业务风险
4.重读值:通过重写数据来防止脏写,以在覆盖数据之前验证它是否保持不变
5.版本文件:将更新记录下来,以便可以对它进行重新排序
6.业务风险评级:使用每个请求的业务风险来动态选择并发机制
Saga的结构:一个Saga包含三种类型的事务
1.可补偿性事务:可以使用补偿事务进行回滚的事务。必须支持回滚,因此有一个补偿事务。
2.关键性事务:Saga执行过程的关键点。如果关键性事务运行成功,则Saga将一直运行至完成。关键性事务不见得是一个可补偿性事务,或者可重复性事务。但是它可以是最后一个可补偿的事务或者第一个可重复的事务。这是Saga事务的成败关键点。
3.可重复性事务:在关键性事务之后的事务,保证成功。确保可以完成。不需要回滚,并且保证可以能够完成。
状态机
状态机由一组状态与一组由事件触发的状态之间的转换组成。每个转换都有一个动作。
事务性消息
事件溯源
事件溯源是构建业务逻辑和持久化聚合的另一种选择。它将聚合以一系列事件的方式持久化保存。每个事件代表聚合的一次状态变化。应用程序通过重放事件来重新创建聚合的当前状态。
什么是事件溯源
事件溯源是一种以事件为中心的技术,用于实现业务逻辑和聚合的持久化。聚合作为一系列事件存储在数据库中。每个事件代表聚合的状态变化。聚合的业务逻辑围绕生成和使用这些事件的要求而构建。
事件溯源通过事件来持久化聚合
事件溯源将每个聚合作为一系列事件来持久化保存。
当应用程序创建或更新聚合时,它会将聚合发出的事件插入到EVENTS表中。应用程序通过从事件存储中检索并重放事件来加载聚合。具体地说,事件聚合应该包含以下三个步骤:
1.加载聚合的事件
2.使用其默认构造函数创建聚合实例
3.调用api()方法遍历事件
ORM框架通过执行一个或者多个select语句检索当前对象的持久化状态,并使用其默认构造函数实例化对象,从而加载对象。它使用反射机制来初始化这些对象。事件溯源的不同之处在于使用事件完成实例化内存状态的重建
事件代表状态的改变
聚合方法都和事件相关
业务逻辑通过调用聚合根上的命令方法(command method)来处理对聚合的更新请求。
传统的应用程序中,命令方法通常会验证参数,然后更新一个或者多个聚合字段。
基于事件溯源的应用程序中的命令方法则通过生成事件来起作用。调用聚合命令方法的结果是一系列事件,表示必须进行的状态更改。这些事件保存在数据库中,并应用于聚合以更新其状态。
处理(process)一个命令会生成事件,但不会更改聚合的状态。聚合状态的更改通过应用(apply)事件来完成
创建聚合的步骤如下:
1.使用聚合的默认构造函数实例化聚合根
2.调用process()以生成新事件
3.遍历新生成的事件并调用该apply()方法来更新聚合状态
4.将新事件保存在保存在事件存储库中
更新聚合的步骤如下:
1.从事件存储库中加载聚合事件
2.使用其默认构造函数实例化聚合根
3.遍历加载的事件,并在聚合根上调用apply()方法
4.调用其process()方法以生成新事件
5.遍历新生成的事件调用apply()方法来更新聚合的状态
6.将新事件保存在事件存储库中。
幂等方式的消息处理
基于关系型数据库事件存储库的幂等消息处理
基于非关系型数据库事件存储库的幂等消息处理
事件存储库
使用事件溯源的应用程序将事件存储在事件存储库中。事件存储库是数据库和消息代理功能的组合。它表现为数据库,因为它具有通过主键插入和检索聚合事件的API。它表现为消息代理,因为它具有一个用于订阅事件的API
事件聚合的好处和弊端
事件聚合的好处
1.它可靠地发布领域事件,这在微服务架构中十分有用
2.保留了聚合的历史记录,这对于实现审计和监管的功能非常有帮助
3.最大限度地避免对象与关系的“阻抗失调”问题
4.为开发者提供一个时光机
事件聚合的弊端
1.这类编程模式有一定的学习曲线
2.基于消息传递的应用程序的复杂型
3.处理事件演化有一定的难度
4.删除数据存在一定难度
5.查询事件存储库通常很困难
传统持久化技术
传统的持久化技术将类映射到数据库表,将这些类的字段映射到数据库表中的列,将这些类的实例映射到数据库表中的行。
好处
1.效果很好
弊端
1.对象与关系的阻抗失调
1.1“阻抗失配”这一词组通常用来描述面向对象应用向传统的关系数据库(RDBMS)存放数据时所遇到的数据表述不一致问题。
1.2“阻抗失配”产生的原因是因为对象模型与关系模型之间缺乏固有的亲合力。
“阻抗失配”所带来的问题包括:类的层次关系必须绑定为关系模式(将对象类映射为关系表),ID生成,并发访问以及下面提到的一些问题。
1.3应用和数据存储方式的不一致是导致这些问题的主要原因。这个问题在很多方面已经变得十分明显,如:产品上市的周期,应用设计、开发和维护的费用,代码维护和扩展性,以及能够满足响应时间和性能需求的硬件配置和结构。在这些方面使用不同的数据存储方式所得到的结果将有很大的不同。
2.缺乏聚合历史
3.实施审计功能将非常繁琐且容易出错
4.事件发布凌驾于业务逻辑之上
微服务架构中实现查询
API组合模式
这是最简单的方法,应尽可能使用。它的工作原理是让拥有数据的服务的客户端负责调用服务,并组合服务返回的查询结果。
参与者
1.API组合器:它通过查询数据提供方的服务来实现查询操作
2.数据提供方服务:拥有查询返回部分数据的服务
API组合模式的设计缺陷
1.确定架构中的哪个组件是查询操作的API组合器
2.如何编写有效的聚合逻辑
API组合模式的好处和弊端
弊端
1.增加了额外的开销
2.带来可用性降低的风险
3.缺乏事务数据一致性
CQRS(Command Query Responsibility Seperation,命令查询职责分离)
CORS比API组合模式更强大,但也更复杂。它维护一个或多个视图数据库,唯一的目的是支持查询
CQRS 命令和查询责任分离数据存储读取和更新操作的模式。 在应用程序中实现 CQRS 可以最大程度地提高其性能、可伸缩性和安全性。 通过迁移到 CQRS 而创建的灵活性使系统能够随着时间的推移更好地发展,并防止更新命令在域级别导致合并冲突。
微服务查询过程中常遇到的三个问题
1.使用API组合模式检索分散在多个服务中的数据会导致昂贵、低效的内存中连接
2.拥有数据的服务将数据存储在不能有效支持所需查询的表单或者数据库中
3.隔离问题的考虑意味着,拥有数据的服务不一定是会实现查询操作的服务
所有这三个问题的解决方案都是CQRS
CQRS隔离命令和查询
CQRA将持久化数据模型和使用数据的模块分为两个部分:命令端和查询端
命令端模块和数据模型实现创建、更新和删除操作(CUD,例如HTTP POST DELETE PUT)。
查询端模块和数据模型实现查询(例如HTTP GET)。查询端通过订阅命令端发布的事件,使其数据模型与其命令端模型保持一致
CQRS的好处和弊端
好处
1.在微服务架构中高效地实现查询
2.高效地实现多种不同的查询类型
3.在基于事件溯源的应用程序上实现查询
4.更进一步地实现问题隔离
弊端
1.更复杂的架构
2.处理数据复制导致的延迟
API Gateway
实现一个服务,该服务是是外部API客户端进入基于微服务应用程序的入口点。它负责请求、API路由、身份验证等各项功能
后端前置模式(Backends for frontends, BFF)
为每个客户端提供一个API GateWay。每个API模块都成为自己的独立API Gateway,由对应的客户端团队开发和运维
后端前置模式的好处
1.明确界定职责
2.API模块彼此隔离,从而提高了可靠性。一个行为不端的API不会轻易影响到其他API
3.提供可观测性,因为不同的API模块是不同的进程
4.每个API都是可独立扩展的
5.后端前置模式可以减少启动时间。因为每个API Gateway都是一个更小、更简单的应用程序
响应式编程
使用传统的异步回调方法编写API组合代码很快就会导致“回调地狱”。代码将纠结成一团,难以理解,且容易出错,尤其是当组合同时使用并发请求和顺序请求时。更好的方法是使用响应式方法,以声明式风格编写API组合代码。JVM响应式抽象包括:
1.Java 8 CompletableFutures
2.Project Reactor Monos
3.RxJava Obeservable
4.Scala Futures
API Gateway职责
1.请求路由
2.API组合
3.边缘功能
4.协议转换
5.成为应用程序架构中的公民