事件驱动的体系结构和事件采购在过去几年中尤其受到关注。这种趋势是由于我们在构建具有弹性和可扩展性的模块化系统之后努力的结果。微服务是经常在这种情况下使用的术语。在我看来,微服务只是实现有界上下文的一种方式。模块化系统的核心是模块的边界,如何识别这些边界的最有前途的想法是Eric Evans的Domain Driven Design引入的战略设计。它可以帮助您识别/发现模块的边界(有界上下文),并描述这些有界上下文如何相互关联的方式(上下文映射)。
领域事件是无所不在的语言的核心
虽然在Eric的书中没有明确提到,Domain Events很好地促进了DDD概念的概念。Alberto Brandolini的Event Storming等技术将重点从技术层面转移到组织/业务层面。我们不讨论像ButtonClickedEvent这样的UI层事件,而是关于域事件,它们是业务领域的一部分,并且由业务专家说出并理解。这些领域事件是一流的概念,提供了形成所有参与者(领域专家,开发人员......)同意的普适语言的好方法。
用于跨上下文通信的域事件
域事件可用于促进有界上下文之间的通信。假设我们有一个网上商店有三个有界上下文:订单,交货,发票。
订单上下文的域事件是订单接受。发票和交货上下文对此事件的发生感兴趣,因为它会导致一些内部进程启动。
脱钩神话
域事件的使用可帮助您开发分离的模块。域事件不关心不可用的模块,它们描述了过去发生的事情。它取决于其他模块处理事件的速度。你得到的是一个设计灵活的系统。
除了时间解耦之外,域事件还有另一个优势,至少乍一看:Order上下文不必知道发票和发货上下文会监听其事件。实际上,它甚至不需要知道这些背景存在。
这很酷,但具有挑战性的部分是事件的内容(有效载荷)。哪些数据会加入到事件中?
简单的答案:事件溯源!
事件是有用的,所以为什么不给它们尽可能多的权力(和责任)。这是Event Sourcing的基本理念。您不是通过更新其数据(CRUD)而是通过应用事件流来存储聚合的状态。
除了可以重放事件以重建应用程序状态这一事实之外,事件源的一个重要特性是您可以免费获得完整可靠的审计日志。因此,当需要这样的审计日志时,在评估持久性策略时,必须考虑事件源。
事件溯源只是一种持久性策略吗?
您可能想知道为什么我直接从Domain Events到持久性策略,因为这些概念显然在不同的层/抽象级别上工作。
......这就是我的观点:事件溯源是由单个有界上下文做出的本地决策!这些事件不应该暴露在外面!如果另一个有界上下文使用事件溯源,则其他有界上下文不了解彼此的持久性策略,因为他们不知道也不关心。
如果您在全局范围内使用事件溯源,则需要公开共享持久层数据库。不同的有界上下文在(关系)数据库中共享数据是一个坏主意。
共享事件其实与共享数据库表一样,我们都会分享持久性细节。
有一条出路
我仍然认为域事件非常适合有界上下文之间的通信,但这些事件不应与事件源的事件相对应。
我提出的解决方案是合乎逻辑的结果:无论您是使用CRUD还是事件采购方法来实现持久性,都可以将域事件发布到全局事件存储。这些域事件是您的有界上下文的公共API。如果您更喜欢在有界上下文里面使用事件源,则将这些事件存储在本地事件存储中,该存储只能从此有界上下文中访问。
下面有两种方式:
1. 使用已发布语言开放主机服务:准确发布一个域事件,其中包含其他有界上下文可能需要的所有数据。在DDD术语中,人们将其称为具有已发布语言的开放主机服务。
当Order发生一个事件OrderAccepted,事件的内容应该包含Order期望其他有界上下文感兴趣的所有数据...所以希望Invoice和Delivery上下文找到他们需要的所有信息。
2. Customer/Supplier:发布多个专用域事件,每个事件使用者一个。您必须与另一方(消费者)讨论每个特定的域事件,而不必定义共享模型。DDD称这种关系为客户/供应商。
订单生成的事件OrderAccepted发生后,导致每个对每个有界上下文消费者发布一个域事件:InvoiceOrderAccepted和DeliveryOrderAccepted。
我不想讨论两种选择的利弊。我只想强调您可以自由选择域事件的数量及其有效负载。
这是一个不容小觑的优势,因为您可以决定如何改进Bounded Context的API,而不是致力于Event Sourcing所需的事件。
结论
将持久性细节暴露给外部世界是一种众所周知的反模式。在谈论持久性时,我们首先考虑数据库表,但是暴露保存在数据库中事件(Event Sourcing)同样也是一种反模式。