最佳事件溯源数据库策略

36

我想建立一个小型事件溯源库。 我在网上阅读了一些教程,到目前为止都理解了。

唯一的问题是,在这些不同的教程中,有两种不同的数据库策略,但没有任何注释说明为什么使用他们选择的方法。

所以,我想问你的意见。 并且重要的是,为什么你喜欢你选择的解决方案。

  1. 第一种解决方案是创建每个事件的一个表结构。

  2. 第二种解决方案是只创建一个通用表结构,并将事件保存为序列化字符串存储在一个列中。

在这两种情况下,我不确定他们如何处理事件更改,也许他们会创建一个全新的事件。

此致敬礼

6个回答

48

我自己构建了一个事件溯源库,并选择了选项2,以下是原因。

  • 您按聚合ID而不是事件类型查询事件流。
  • 如果所有事件都在不同的表中,则按顺序再现事件将很麻烦。
  • 升级事件会有点麻烦。

有人认为可以按每个聚合存储事件,但这取决于项目的要求。

我有一些关于如何使用事件流的文章,您可能会发现有帮助。


你(优秀的)博客似乎在显示旧文章时出现了问题(包括你在这里附加的链接)。 - itamar
谢谢您提醒我问题所在,现在已经修复了。 - Codescribler

15

解决方案是创建一个通用表的数据库结构,并将事件作为序列化字符串保存到一个列中。

这是迄今为止最好的方法,因为回放事件更简单。关于事件溯源,我的建议是:这是一种很好的模式,但你应该小心,因为并不是每件事情都像看起来那么简单。在我所工作的系统中,我们保存了每个聚合的事件流,但我们仍然有一组规范化的表,因为我们不能接受为了获取对象的最新状态必须运行所有事件(快照有所帮助,但并不是完美的解决方案)。因此,是的,事件溯源是一个好的模式,它为您的实体提供了完整的版本控制和完整的审计日志,它应该只用于此,而不是作为规范化表集的替代品,但这只是我的建议。


7
将实体的最新视图(最终一致性)与事件溯源并用非常兼容,事实上它们经常一起使用。事件溯源只是意味着事件是数据的唯一权威来源,因此您的物化视图/投影必须从中派生。把这些视图视为方便的东西,可以随时清除/重建,这样就可以了。 - AlexG
5
完成您的回答就好了,没必要这么无礼 :) - AlexG
2
实际上,这只是实现CQRS的一种方式,它是围绕这样一个理解设计的,即每次都基于纯事件构建可能在许多情况下效率低下。 - Jacob Zimmerman

6
我认为最好的解决方案是选择#2。如果您使用像mysql这样的事务性数据库,甚至可以将当前状态和相关事件一起保存。
我真的不喜欢也不推荐#1的解决方案。
如果你关心的是#1的事件版本升级问题,那么对于每个新变化,请声明一个新类。不要太懒或过于追求重用。让订阅者了解更改,并给他们事件版本。
如果你对#1有查询/解释事件的疑虑,那么以后您可以在任何时候(从原始数据库)轻松地将您的事件推送到nosqldb或eventstore。
此外,我用于事件源代码库的模式是这样的:
public interface IUserCreated : IEventModel
{

}

public class UserCreatedV1 : IUserCreated
{
    public string Email { get; set; }
    public string Password { get; set; }
}

public class UserCreatedV2 : IUserCreated
{
    // Fullname added to user creation. Wrt issue: OA-143

    public string Email { get; set; }
    public string Password { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
}

public class EventRecord<T> where T : IEventModel
{
    public string SessionId { get; set; } // Can be set in emitter.
    public string RequestId { get; set; } // Can be set in emitter.
    public DateTime CreatedDate { get; set; } // Can be set in emitter.
    public string EventName { get; set; } // Extract from class or interface name.
    public string EventVersion { get; set; } // Extract from class name
    public T EventModel { get; set; } // Can be set in emitter.
}

public interface IEventModel { }

所以,在领域和代码库中明确事件版本控制和升级。在部署新事件的源之前,实现订阅者对新事件的处理。如果不需要,请勿允许外部订阅者直接消费领域事件;可以使用集成层或类似的东西来处理。希望我的想法能对您有所帮助。

2
我了解一种事件溯源方法,大致包括以下步骤:
1. 创建两个表:聚合表和事件表; 2. 取决于您的用例,可以选择以下操作之一: a. 在聚合表上创建并注册记录,生成一个ID,版本号为0,以及一个事件类型,并在事件表上创建一个事件; b. 从聚合表中检索ID或事件类型相关的事件,应用业务逻辑,然后更新聚合表(版本号和事件类型),最后在事件表上创建一个事件。 3. 尽管这种方法会更新某些聚合表字段,但它保持事件表只能追加记录,并提高了性能,因为您可以在聚合表中获取最新版本的聚合记录。

你如何表示事务或工作单元?我们在根聚合内拥有子聚合来组织我们的代码。我们有依赖于横切聚合(日志、安全等)的聚合。一个由Lucene支持索引的巨型表是我的首选。不要忘记在DDD和ES中使用用例。 - jenson-button-event

1

我会选择#2,如果你真的想通过事件类型进行高效搜索,我只需在该列上添加索引即可。


0

以下是访问涉及此案件主题数据的两种策略。 1)当前状态和2)事件排序。 使用当前状态,我们处理事件,但仅保留主题的最后状态。 使用事件排序,我们保留事件并通过每次需要状态时处理事件来重建当前状态。 事件排序更可靠,因为我们可以跟踪导致当前状态发生的所有事件,但它肯定不高效。保留中间状态(快照)而不仅仅是最后一个状态以避免一直重新处理所有事件是常识。现在我们既有可靠性又有性能。

在加密货币中,有事件排序和本地快照-本地名称是因为区块链是分布式的,数据被复制。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接