通过事件风暴和DDD建立微服务时优先考虑事件

本文是讨论在使用DDD+CQRS+EventSourcing中事件建模的重要性,通过抓取事件建模这个线索,能够起到纲举目张的效果。

DDD实现中的一个非常大错误解释
领域驱动设计(DDD)是一种奇妙的技术,试图使我们的设计更接近于业务领域 。

理论上,我们在实现中越接近业务领域,我们的软件就越好。有了这个目标,DDD在我们的行业中非常成功。

但是,作为架构师和设计师,我们在软件架构上做出的早期决定有一种尴尬的情感。

这种对架构的强调并不是DDD的意图。DDD一些实践使用经验和解释不幸地加剧了​​这一点,到处存在对无处不在的统一语言的误解,特别是对领域对象。

领域对象成为一个主要的问题,因为他们纠缠了软件设计中多个方面的关注,这是打着无所不在的旗帜。 这些领域对象成为实体,消息的有效载荷 ,表单的后台对象.它们在系统中是混杂的,并且是纠缠的,且是系统脆弱性的来源。

当人们将域对象打包到库中以便重用,情况变得更糟糕。为了重用大幅度增加了其重量,这其实是增加了域对象的僵化效果 ,这就是系统的脆弱部分,导致系统无法演进推进,因为这些脆弱的臃肿的领域对象无处不在地被使用。

将域对象库进行共享是无法演进的,如果不进行锁保护就没有一切。

由此,出现了注重领域对象和实体的框架,其目标是使事情更容易,但不一定是使事情变得更简单或改善您的设计 。 这个方面不好的例子包括Rails,Grails,Spring Roo,Entity Framework,N / Hibernate等。

实体、数据库和领域类首先作为一种方法变得非常受欢迎,同时导致了这些问题,因为它成为企业分层架构中的默认架构风格 。 整个系统发展爆炸成不可改变的僵化系统原因是,领域类中有一个东西从一个领域的无所不在,变成了跨所有领域的规范。

此时你会有一个顿悟!

这个东西并不存在于设计的早期阶段 ...

... 它是发生过的某个事情 。

我们有一个名字,对于在软件设计中发生的事情,称为事件 。

“事件优先”
对DDD顿悟是,发生的事情应该是一些软件设计的第一个考虑。

这种较新的方法被称为事件优先 。

事件能够更好地捕获领域或系统的无处不在语言。通常,描述系统的最简单方法是根据发生的事情描述 。

发生的事情就是事件。

事实证明,如果您正在开发现有系统,或者正在开发一个新系统,这种方法效果很好。

事件风暴技术是这个旅程的第一个设计步骤 。

事件风暴
事件风暴是一个协作活动,将领域专家,技术架构师和设计师聚集在一起,发现系统或上下文的无处不在的语言。 目的是试图捕获系统发生的事情 ,也就是事件。

使用贴纸以一个粗略的他们可能会发生的顺序重现这些事件,尽管还没有考虑它们是怎么发生的,,或是什么结构支撑其发生的。

这类似于一种侦探技术,你可以认为自己是到达犯罪现场的一个侦探,只是询问自己或团队工作人员有关系统的事实,事实是什么?

将你限制到事件, 使用事件描述你知道的事实 ,它们是你正在工作的系统中发生的,并起到影响效果的。


什么是好事件?
当设计你的事件,也就是试图使得事件这个概念从隐式变成显式 。如果几个低级事件表明一个更高级别的事实,那么需要让该事实成为一个事件。 同样,如果一个高层次的事实可以被看作是由较小规模的事件组成,那么确保你也有这些小事件。

有很多事件并不是一个问题。

必须让你的事件是完全自给自足 ,自我描述 。

使您的事件技术化且实施不可知的。

一旦你拥有一套愉快的事件,当你的上下文中出现错误时可以浏览一下发生的事件。

此方法可帮助您提出问题:“我们需要知道什么事件?” ,这是一个强大的技术,能帮助探索一些边界条件和假设,这些条件和假设往往影响软件构建的复杂度 。

事件是不可变的,毕竟他们是事实。


通过事件追寻因果关系
通过探究事件发生的假设前提场景,就可以获得创建事件的因果关系图,

没有因果关系的事件越多 ,您在最终设计的新兴架构中生成和处理这些事件的选项就越多 。

通过“有界上下文”绑定
现在是时候引入一个围绕在无处不在的语言周围的边界了,也就是围绕事件所在边界。

“Life Preserver”图表只是一个由几个同心圆组成的有用工具,它们是您的“有界上下文”和“反腐败层”的直观表示 。

有界上下文与团队和团队结构相关。
一个团队通常需要照顾和开发一个或多个有界上下文,但是具有不同目标的两个团队不应该理想化地放在相同的有界上下文中工作,因为通常在这种情况下变化速度的不同导致摩擦和责任稀释。

如果一个团队继承一个“Heritage”(Legacy遗留)软件系统,它应该保留在它自己的Bounded Context中,因为它将展示它自己的无处不在的语言的时间和地点以及它最初开发的团队。不要诱惑合并遗留系统的背景到您现在系统的当前上下文中,你将稀释无处不在的语言,需要人们理解两个系统。

有界上下文很少作为嵌套概念 。 团队的工作重点是朝向明确的目标,有界的上下文遵循这个模型。

最后,到结构...

首先, 发现系统中存在的无状态服务 。 这些是不保持任何状态消费改变以及推出事件的服务。

其次, 考虑使用Repository服务来捕获状态,其中用于修改状态以及读取状态的模型是相同的。

警惕数据持久性技术的漏洞抽象,并使您的存储库与您选择的任何技术无关。

最后如果你需要根据修改和查询不同状态改变性能特征和模型 ,可以看看微服务使用CQRS的实现...

打败状态的复杂性
复杂性,关注的纠缠,是软件开发项目的不那么沉默的杀手。 它软件中意外或偶然的复杂性的最大原因 我们可以通过组织 ,减少和封装来去除它,并以事件优先去寻找 。

软件意外复杂性的第二大原因是对错误的业务使用错误的数据模型。

领域驱动设计(DDD)为我们提供了用于存储和检索数据的简单存储库模式,但不幸的是这种模式倾向于纠缠写和读的问题 。

如果写和读的模型相同,这不是一个问题,事实上它有一些真正的优势,因为您可以确保在存储库中的数据集的完全一致性。

但是当写和读的需求不同,而且经常发生这种情况,我们往往需要更多的东西。

除了使用的模型,事实证明,“写”和“读”有更进一步不同的特点,比如:
1.容错
2.性能和可扩展性
3.一致性

当同时操作和读取数据时,我们最终会遇到“系统复杂性”,这又导致对系统组件的难以理解和改变。

分离关注:命令查询责任分离
解决这些问题的第一步是分离操作和读取时的两个模型。 这是命令查询责任分离(CQRS) 模式的 核心 。

这种方法的关键特征是解开写和读模型之间的缠结,分解为两个系统组件: 写和读模型 。

命令和写入模型
写模型捕获报告系统中的一些已被修改的重要状态, 这就是为什么这个模型通常被称为修改模型。

命令由写入模型接收并被处理以产生发生了什么修改的事件。

命令表示写入模型的事务一致操作,并应完全成功或失败。 写模型将维持任何需要的状态,以任何优化的形式,以产生它负责的事件。

聚合的作用就是接受命令并执行它以产生一个或多个有效事件 。

事件代表完整、完全、自描述和不可变的关于系统的事实。反过来,命令理想地包含产生期望的有效事件所需要的所有内容。

在这方面,重要的是要记住,事件不仅仅是处理命令的副作用,它们也是命令的实际数据结果。 命令和聚合只能产生一组重要的事件,也就是关于我们领域的事实。

如果Command没有包含所有必要的细节来产生Events,你该怎么办? 有时,命令的处理需要聚合获得系统的其他部分的数据或者更精确的事件。有时,此数据可以来自本地(到有界上下文)视图。

当然,这也代表存在竞争问题了,其中聚合尝试执行命令,而必需信息在支持视图或本地高速缓存中尚不可用。

在最终一致的系统中这是正常状态,隔离和分区是在牺牲系统级一致性(例如在可适应性,反垃圾邮件,基于微服务的系统)中才会体现价值。如果聚合正在处理错误或恶意的命令,也会发生这种情况。

如果聚合处理命令失败,那么该命令将被报告为失败。 然后,发送方有责任使用其更大的业务环境知识来决定是否回退和重试命令(一次或多次使用增加的延迟是一个常见的策略),或者失败较大的业务操作。

在某些方面,该策略类似于使用断路器,其中电路是启动器和聚合体之间的相互作用,并且最终电路对于试图进行的商业操作而断开。

在发送方是Saga的情况下,则可能需要附加处理以便通过补偿命令执行回滚。

查询和读取模型
写入模型提供了一个很好的起点,但它然后提出了问题“如何查询我的数据? 。

在CQRS中,此责任落在一个(或多个) 读取模型中 。

读取模型负责监听多个不同的事件并维护状态的优化版本,其可以以高效的方式满足响应一种或多种类型的查询的需要 。

事件作为模型之间的通信
那么Read Models读模型如何更新呢? 通过订阅由系统中的写入模型发出的一个或多个事件流 。

读模型感兴趣的事件将由读模型提供结果的查询或查询来决定。

事件是关键
使用CQRS依赖于设计正确的事件 。

事件是领域中无处不在语言的最重要的部分,因此应该使这些领域概念尽可能显式。

使用事件源减少状态的脆弱性

事件源是您记录来自于聚合事件的地方 。

事件存储器保证以同样顺序可靠地存储您的事件,然后对这些事件可以查询和重放。

将事件存储在一个健壮和有弹性的地方,以便可以从任何时间点的事件重新创建聚合和视图 。

这意味着聚合和视图可能很脆弱。

快照代表了一种折衷,即缩短重放时间,可以重试版本,但数据迁移会有一定程度耦合。

Going "Events-First" for Microservices with Event