使用RDBMS作为事件采购存储

如果我正在使用RDBMS(例如SQL Server)来存储事件源数据,模式可能是什么样子?

我已经看到抽象意义上谈到的一些变化,但没有具体的。

例如,假设有一个“产品”实体,并且对该产品的更改可以以价格,成本和描述的forms出现。 我很困惑我是否会:

  1. 有一个“ProductEvent”表,其中包含产品的所有字段,其中每个更改意味着该表中的新logging,以及适当时候的“谁,什么,何处,何处,何时以及如何”。 当成本,价格或描述发生变化时,会添加一个新的行来表示产品。
  2. 将产品成本,价格和说明存储在与外部关系关联的“产品”表中的单独表中。 对这些属性进行更改时,请根据需要使用WWWWWH编写新行。
  3. 将“WWWWWH”和表示事件的序列化对象存储在“ProductEvent”表中,这意味着事件本身必须在我的应用程序代码中被加载,反序列化并重新播放,以重build给定产品的应用程序状态。

特别是我担心上面的选项2。 极端地说,产品表几乎是每个属性一个表,在哪里加载给定产品的应用程序状态将需要从每个产品事件表加载该产品的所有事件。 这张桌子爆炸味道对我来说是错误的。

我确信“这要看情况”,虽然没有一个“正确的答案”,但我试图去了解什么是可以接受的,什么是完全不能接受的。 我也知道NoSQL在这里可以提供帮助,在这里可以根据聚合根存储事件,这意味着只有一个数据库请求来获取事件来重build对象,但是我们没有使用NoSQL数据库这一刻,我感觉周围的替代品。

事件存储不应该需要了解事件的具体领域或属性。 否则,对模型的每个修改都将导致不得不迁移数据库(就像在老式的基于状态的持久化中一样)。 所以我不会推荐选项1和2。

下面是Ncqrs中使用的模式。 如您所见,“Events”表将相关数据存储为CLOB(即JSON或XML)。 这对应于你的选项3(只有没有“ProductEvents”表,因为你只需要一个通用的“Events”表)在Ncqrs中,通过“EventSources”表映射到你的Aggregate Roots,每个EventSource对应一个实际聚合根。)

Table Events: Id [uniqueidentifier] NOT NULL, TimeStamp [datetime] NOT NULL, Name [varchar](max) NOT NULL, Version [varchar](max) NOT NULL, EventSourceId [uniqueidentifier] NOT NULL, Sequence [bigint], Data [nvarchar](max) NOT NULL Table EventSources: Id [uniqueidentifier] NOT NULL, Type [nvarchar](255) NOT NULL, Version [int] NOT NULL 

Jonathan Oliver的Event Store实现的SQL持久性机制基本上由一个名为“Commits”的表格组成,BLOB字段为“Payload”。 这与Ncqrs几乎相同,只是它以二进制格式序列化事件的属性(例如,增加了encryption支持)。

格雷格·扬(Greg Young)推荐了一个类似的方法,正如Greg的网站上广泛logging的那样

他的典型“事件”表的模式是:

 Table Events AggregateId [Guid], Data [Blob], SequenceNumber [Long], Version [Int] 

那么你可能想看看Datomic。

Datomic是一个灵活的, 基于时间的事实数据库,支持查询和连接,具有弹性可伸缩性和ACID事务。

我在这里写了一个详细的答案

您可以在这里看到Stuart Halloway的讲话,解释Datomic的devise

由于Datomic存储了事实,您可以将其用于事件采购用例等等。

GitHub项目CQRS.NET有几个具体的例子说明如何用几种不同的技术来完成EventStore。 在写这篇文章的时候,在SQL中有一个使用Linq2SQL和一个SQL模式的实现, MongoDB有一个, DocumentDB (如果你在Azure中是CosmosDB),还有一个使用EventStore (如上所述)。 Azure中还有更多类似于平面文件存储的表存储和Blob存储。

我想这里的重点是他们都符合相同的委托人/合同。 它们都将信息存储在一个地方/容器/表格中,它们使用元数据来识别来自另一个事件的事件,并“将”整个事件保存原样 – 在某些情况下序列化,支持技术。 因此,根据如果select文档数据库,关系数据库甚至是平面文件,有几种不同的方法可以达到事件存储的相同意图(如果您随时改变主意并发现需要迁移或支持多个存储技术)。

作为该项目的开发人员,我可以分享一些我们所做的select。

首先,我们发现(即使使用唯一的UUID / GUID而不是整数),出于战略原因,连续ID出于多种原因,因此只有一个ID对于一个密钥来说不够唯一,所以我们将主ID键列与数据/对象types创build什么应该是一个真正的(在你的应用程序的意义上)唯一的关键。 我知道一些人说你不需要存储它,但是这取决于你是否是绿地或者与现有系统共存。

由于可维护性的原因,我们坚持使用单个容器/表/集合,但是我们确实为每个实体/对象使用了单独的表格。 我们在实践中发现,这意味着应用程序需要“创build”权限(通常来说,这不是一个好主意……通常总是有例外/排除),或者每当新的实体/对象出现或被部署时,存储容器/表/集合需要被做。 我们发现这对本地开发来说太痛苦了,而且对于生产部署来说也是有问题的。 你可能不会,但那是我们现实世界的经验。

另外要记住的是,要求动作X发生可能会导致许多不同的事件发生,从而知道由命令/事件生成的所有事件/什么是有用的。 他们也可能跨越不同的对象types,例如在购物车中推“购买”可能触发账户和仓储事件触发。 一个消费应用程序可能想知道所有这些,所以我们添加了一个CorrelationId。 这意味着消费者可以要求所有由于他们的要求而引发的事件。 你会在架构中看到。

特别是对于SQL,我们发现,如果索引和分区没有被充分使用,性能真的成了一个瓶颈。 如果您正在使用快照,记住事件将需要以相反的顺序进行stream式传输。 我们尝试了几个不同的索引,发现在实践中,需要一些额外的索引来debugging真实世界的应用程序。 你会再次在模式中看到。

其他生产中的元数据在基于生产的调查中非常有用,时间戳让我们了解事件持续发生的顺序。 这给了我们一些特别大量的事件驱动系统的帮助,这个系统引发了大量的事件,给我们提供了关于networking和networking系统分布等信息。

可能的提示是devise,然后是“慢慢变化的尺寸”(types= 2)应该帮助你覆盖:

  • 事件发生顺序(通过代理键)
  • 每个状态的持久性(从 – 有效到有效)

左侧折叠function应该也可以实现,但是您需要考虑将来的查询复杂性。