事件驱动的编程模型使事件成为一个重要的架构元素。事件触发领域逻辑,并参与领域模型内的各种交互。在更一般的术语中,事件是通知的一种形式。在前面的小节中,您看到了两种类型的通知,它们的语义略有不同:命令和事件。我们将经常使用术语“event(事件)”来指代这两种类型的通知,并且只有在需要以不同的方式处理命令时才破例。
在前面的小节中,您看到了一个Debit(借记,通俗说就是取款)事件的例子,它触发了您的银行帐户的取款操作,以及一个DebitOccurred的事件,它通知有兴趣的各方,某个帐户发生了一笔取款操作。您已经根据在领域模型中执行的操作来命名这些事件;这些事件说明了领域的语言。它们被称为domain events(领域事件)。
事件的一个重要特征是它们是不可变的。这是很直观的,因为你已经看到,事件是系统中已经发生的事情。那么,你怎么能改变过去发生的事情呢?
作为银行的客户,鲍勃在1989年3月1日时候,住在地址A处。2010年6月6日,他搬到了另一个地址B。处理这种情况的一种常见方法是在customer实体中更新Bob的地址。但是,当您谈论事件时,如何发出一个更新语句,并在客户记录中更改Bob的地址?他住在地址B的事实并没有使他曾经住在地址a的事实无效。有些人可能会说,数据库服务器维护了一个日志,它仍然表明,在过去的某个时候,Bob曾在地址A中生活过。但是,毕竟,数据库日志不是我们模型的一部分,而Bob在某个时间点住在某个地址上的事实是我们正在设计的领域模型的语义的一部分。让我们看看我们领域模型中发生的事件序列;表1.6显示了Bob的时间轴变化的历史。
表1.6
时间点 | 操作 | 时间 |
---|---|---|
T0 | Bob开户了账户,地址属性是A。 | 触发一个事件 AddressChanged(Bob, _, A, T0) |
T1 | Bob的地址变为了B | 触发一个事件 AddressChanged(Bob, A, B, T1) |
这里我们讨论的是一种与标准关系型数据库模型不同的建模方法。我们讨论的是不能更改的事件,但是它们被应用于领域模型以到达当前的快照。在前面的例子中,您可以在银行的Bob的记录中应用最后一个事件来获取他当前的地址。然而,你不会丢失他曾经住在一个不同的地址的信息。这意味着您不仅拥有该模型的当前快照,而且还拥有生成该快照的整个历史记录的日志。图1.13清楚地说明了这一点。
正如前面提到的,领域事件在架构模型中具有重要的作用,它可以伸缩并响应。到目前为止,我已经给你们简单介绍了它们是什么以及它们如何帮助你们建立起自演变以来的整个历史的模型。这样的域模型称为自跟踪模型,因为领域事件日志使我们的模型在任何时间都可以跟踪。
因此,领域事件是反应领域模型的重要参与者,当您构建自己的模型时,您必须给予它们应有的重要性。Jonas Bonér在他的一个事件驱动架构的演讲中给出了一个很好的总结。他解释说,领域事件是:
唯一可识别的类型---对于每个事件,您都在模型中有一个类型。
独立的行为---每个域事件都包含与系统中刚刚发生的变更相关的所有信息
对消费者可观察的----事件意味着被模型的下游组件,为进一步的操作而被消费。
时间相关的---它可能是领域事件最重要的特征。时间的单调性被构建在事件流中。
如果领域模型可以通过应用所有的领域事件来构造,从时间t0开始,那么您的整个模型可以归结为以下的数学等式:
M(t n ) = ∑(all events from time t 0 till t n )
M(t n)是模型的状态,表示为从t0开始的所有事件的和。
在后面的章节中,您将看到这个等式如何映射到函数式编程中的等价概念。