事件溯源中的时间和时间建模 - Tomasz Jaskula


Tomasz Jaskuła 是巴黎软件咨询公司 Luteceo 的首席技术官和联合创始人。Tomasz 拥有 20 多年作为开发人员和软件架构师的专业经验,曾就职于电子商务、工业、保险和金融领域的多家公司。他主要专注于创建能够提供真正业务价值、与战略业务计划保持一致并提供具有明显竞争优势的解决方案的软件。Tomasz 还是 .NET 平台的 OSS 项目 XOOM 的主要贡献者。
 
DDD 和有界上下文

  • DDD 中最重要的是战略部分。一开始,大约在 2007-2010 年,人们更多地关注 DDD 的战术方面,比如存储库、实体、值对象等模式。我并不是说它们不重要。当然,当涉及到 DDD 实施时,它们是重要的工件,但是当您与业务人员和团队中的其他成员构建无处不在的语言,并且从战略的角度发现业务问题时,它们是整个战略部分。所以这就是让 DDD 有价值的原因。
  • 识别模型的不同上下文是 DDD 的一部分,称为有界上下文。有界上下文表示这个概念的这个术语在这个特定的上下文中确实具有特定的含义,并且在不同的上下文中可能具有另一种含义。
  • 一旦确定了这些上下文,您就必须找到一种沟通和整合的方式。所以这也是战略领域驱动设计的另一个非常重要的部分。
  • 您可以使用领域驱动设计中的实现和战术工具,但您不能只选择其中一个。您必须同时使用战略和战术部分。

DDD 和函数式编程
  • 在函数式编程中,您有这些不变性的概念。在 DDD 中,您有领域事件的概念。领域事件是已经发生的事实,您无法更改它们。你可以承认发生了一些事情。
  • 您拥有这个单元是域建模的重要组成部分,这一事实与函数式编程非常匹配。当您使用诸如事件溯源和 CQRS 架构风格之类的东西时,当您拥有这个意图的命令并且您拥有将命令输入、应用状态和生成新状态的功能时,它可能更明显。

 
时间建模:
  • 所有重要的业务和业务问题都与时间相关。
  • 回到业务上来,大部分时间我都看到我们正在开发的模型并没有将时间作为第一要素,而且时间总是隐含的,就像一切都会立即发生一样。
  • 有一些工件,如工作流、基于时间的业务流程。这些暗示我们时间会流逝,时间对业务很重要。那么时间建模的意思是我们使用基于时间的工件作为第一建模公民。
  • 同样,领域事件,它们与时间和时间流逝有关。需要注意的重要一点是,业务总是考虑包括时间,因此我们必须对其进行明确建模。如果我们不将时间建模为一个明确的东西,这就是许多问题潜入的地方。我们试图解决问题,带来意外的复杂性,从长远来看,这会导致无法维护的软件和大泥球。
  • 有不同类型的时间建模。可以有通过时间产生的触发器。
  • 我提到的另一个工件是我们的领域事件。领域事件是不可改变的事实。商界人士关心的事情。它以过去时的动词形式出现。因为这是事实,我们无法改变它。这是已经发生的事情,我们只能承认。但我们无法改变它。它是不可变的。
  • 在建模和创建域模型时在模型中使用领域事件也是一种时间指示。因为通过发布事件,您对其他对此类领域事件感兴趣的人说,发生了一些事情,由不同的利益相关方听听这些领域事件来对它们采取行动。但与此同时,它并没有说它必须在之后发生。它引入了某种异步关系,你真的不知道是否有人会在一毫秒、一秒或三天内采取行动。
  • 这就像时间的指示,它是由业务人员定义的。大多数情况下,当您不考虑时间时,开发人员或实现域模型的人员都试图在同一时间立即发生所有事情。它会产生问题,因为我们必须创建不同类型的事务模型,所以一切都是以原子方式发生的。但在时间建模方面,情况并非如此。

三种不同类型的时间:
  • 有不同类型的时间建模,并且都在域建模中明确了时间。那些不同的类型是双时态的,三时态的,我知道我们是否可以更进一步,它们都是为了回答不同的问题。
  • 双时态建模基于两个时间轴。
    • 正如你所说,有一个交易时间,有效的时间轴。因此,我们可以回答特定的问题,例如我的系统在某个时间点的状态,或者我的系统在某个时间点的状态应该是什么,并了解发生了什么。
    • 它背后的想法是使用交易时间作为交易发生的时间。例如,如果我们在系统中记录了一些东西,我们会取当前日期,并将其放在交易时间上。但有效时间是自由定义的。
  • 三时态建模案例更进一步,因为您有第三个时间轴,即决策轴。它使查询变得更加复杂,但它也为您提供了更多探索事实的方式。

 
事件溯源
  • 保持状态的传统方式是保存当前状态。事件溯源使用完全不同的以事件为中心的持久性方法。业务对象通过存储一系列状态更改事件来持久化。
  • 因此,每当对象状态发生变化时,都会将一个新事件附加到已生成事件的序列中,并在仅附加存储中。这就是通过重放在对象生命周期生命周期中发生的所有事件来重建业务对象当前状态的方式。
  • 事件溯源是不同的,因为在您确定最重要的事实,即对业务和模型很重要的事件之前,每次发生变化时,您都会生成一部分状态。因此,一个事件将被附加到日志中,添加到事件源日志中,即仅附加日志。
  • 这样,当您查看添加到事件日志中的事件类型时,您就会了解发生了什么。我们究竟如何到达,我们如何到达当前状态。因为所有事件都可以告诉您发生了什么以达到当前状态。
  • 当您只是丢弃以前的状态并用状态的新快照覆盖它时,您无法在传统方法中做到这一点。

 
何时使用事件溯源
  • 事件溯源允许您从不同的角度看待您的领域。因为不仅您知道您是如何获得当前状态的,因此这可以让您了解您的系统或您的域中正在发生的事情。您永远不知道将来如何使用这些事件。因此,它为您提供了更多关于您的域的数据,您可以在未来以不同的方式使用这些数据。因此,这是您在使用事件溯源时免费获得的重要信息,而在使用传统方法时则无法获得。
  • 状态转换是问题空间的一个非常重要的部分,它应该按原样建模,而事件溯源对此有所帮助。所以我们可以看透已经发生的不同事件。不同的状态发生了变化,要真正知道发生了什么。所以这是非常重要的信息。我会说,如果您有一个复杂的业务领域,并且您正在处理领域事件,那么使用事件溯源比传统方法更容易。
  • 我几乎每次都在使用事件溯源来重构旧的遗留项目。所以我们所做的是通过事件将遗留系统与新的事件源上下文同步。即使您的旧系统不是事件源,您也只有当前状态的快照。您可以使用多种技术将遗留系统与新事件源同步,比如微服务。
  • 最简单的方法是在遗留端生成事件。它可以通过使用 Change Data Capture 变更数据捕获 之类的工具来扫描所有发生的事务,遗留系统中的数据源。它们可以产生有意义的事件,您可以在新上下文中收听这些事件,并将旧状态从遗留状态迁移到新上下文。
  • 或者,您可以自己与业务人员一起制作这些活动。您可以确定什么是重要的。如果在遗留方面发生了变化,您可以生成对新上下文有意义的事件,并且您可以以这种方式同步,并以这种方式使用事件源上下文和新上下文或微服务。
  • 或者它发生在我正在从事的一些项目中,遗留系统有一些历史数据,这些数据存储在历史表中。我们可以从历史表中推导出事件和发生的事情。您可以毫无问题地在x绿地(新项目)和棕地(旧项目)中使用它。
  • 这是不容易的。有时您无法访问参与状态更改的所有遗留部分。因此,会丢失一些信息,因为它并不总是一对一的。
  • 但大多数问题,如果你能在遗留端产生一些有意义的事件,然后将它们同步到新的上下文,你将能够链接这两个不同的系统,遗留和新上下文。

事件溯源工具
  • 有事件源数据库。但是您也可以使用具有这种能力的 SQL 数据库。或者您甚至可以使用一些可以帮助您解决问题的库将事件存储在 SQL 关系数据库的表中。所以存储事件没有问题。
  • 在您选择什么之后,这取决于您的限制。因为您可能会遇到性能问题。您有一分钟内发生了数百万个事件,因此您需要一些可靠的东西,有时将其存储在 SQL 数据库中并不能解决问题。
  • 说到双时态,这就是事情变得更加复杂的地方,因为最好的方法是拥有一个双时态兼容的存储。最重要的是您可以在不同的时间轴上运行不同的查询。所以肯定的是,我们使用的数据库或存储必须符合时态数据库相关的标准,否则很难做到。
  • 事实上,双时态数据库有一个标准,称为 SQL 2011。