Cqrs 使用RDBMS作为事件源存储

Cqrs 使用RDBMS作为事件源存储,cqrs,event-sourcing,Cqrs,Event Sourcing,如果我使用RDBMS(例如SQL Server)来存储事件源数据,那么模式会是什么样子 我见过一些抽象意义上的变化,但没有具体的变化 例如,假设有一个“产品”实体,对该产品的更改可以是:价格、成本和描述。我对我是否会: 有一个“ProductEvent”表,该表包含产品的所有字段,其中每个更改意味着该表中的一条新记录,以及“who、what、where、why、when和how”(WWH)(视情况而定)。当成本、价格或描述发生更改时,将添加一个全新的行来表示产品 将产品成本、价格和说明存储在与具

如果我使用RDBMS(例如SQL Server)来存储事件源数据,那么模式会是什么样子

我见过一些抽象意义上的变化,但没有具体的变化

例如,假设有一个“产品”实体,对该产品的更改可以是:价格、成本和描述。我对我是否会:

  • 有一个“ProductEvent”表,该表包含产品的所有字段,其中每个更改意味着该表中的一条新记录,以及“who、what、where、why、when和how”(WWH)(视情况而定)。当成本、价格或描述发生更改时,将添加一个全新的行来表示产品
  • 将产品成本、价格和说明存储在与具有外键关系的产品表关联的单独表中。当这些属性发生更改时,根据需要使用WWH写入新行
  • 在“ProductEvent”表中存储WWW WWH以及表示事件的序列化对象,这意味着必须在我的应用程序代码中加载、反序列化并重新播放事件本身,以便重新构建给定产品的应用程序状态
  • 我特别担心上面的选项2。极端来说,产品表几乎是每个属性一个表,要加载给定产品的应用程序状态,需要从每个产品事件表加载该产品的所有事件。我觉得这张桌子有点不对劲


    我确信“这要看情况而定”,虽然没有单一的“正确答案”,但我正试图对什么是可接受的,什么是完全不可接受的有一种感觉。我也知道NoSQL在这方面很有帮助,事件可以存储在聚合根上,这意味着只需要向数据库发出一个请求,就可以获取要从中重建对象的事件,但是我们目前没有使用NoSQL db,所以我正在寻找替代方案。

    事件存储不需要知道事件的特定字段或属性。否则,对模型的每一次修改都将导致必须迁移数据库(就像老式的基于状态的持久化一样)。因此,我根本不推荐选项1和2

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

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

    格雷格·杨(Greg Young)建议采用类似的方法,如图所示

    他的原型“事件”表的模式如下:

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

    你可能想看看Datomic

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

    我写了一份详细的答复

    你可以看Stuart Halloway讲解Datomic设计的演讲


    由于Datomic及时存储事实,因此您可以将其用于事件源用例等。可能的提示是“缓慢变化的维度”(type=2)后面的设计应帮助您涵盖:

    • 事件发生顺序(通过代理键)
    • 每个状态的耐久性(有效期从-有效期到)
    左折叠函数也可以实现,但您需要考虑未来查询的复杂性。

    GitHub项目有一些具体的示例,说明如何使用几种不同的技术来实现EventStores。在撰写本文时,在中有一个实现,其中一个用于,一个用于(如果您在Azure中,则为CosmosDB),另一个用于(如上所述)。Azure中有更多类似于表存储和Blob存储的功能,这与平面文件存储非常相似

    我想这里的要点是,它们都符合相同的原则/合同。它们都将信息存储在一个位置/容器/表中,它们使用元数据将一个事件与另一个事件区分开来,并“仅”按原样存储整个事件—在某些情况下,按原样在支持技术中序列化。因此,根据您选择的是文档数据库、关系数据库还是平面文件,有几种不同的方法可以达到事件存储的相同目的(如果您在任何时候改变主意,发现需要迁移或支持多种存储技术,这将非常有用)

    作为项目的开发人员,我可以分享一些关于我们所做选择的见解

    首先,我们发现(即使使用唯一的uuid/guid而不是整数)出于许多原因,顺序ID是出于战略原因出现的,因此仅仅拥有一个ID对于一个键来说不够唯一,因此我们将主ID键列与数据/对象类型合并,以创建真正(在应用程序的意义上)唯一的键。我知道有些人说你不需要存储它,但这取决于你是绿地还是必须与现有系统共存

    出于可维护性的原因,我们坚持使用单个容器/表/集合,但我们确实为每个实体/对象使用了单独的表。我们在实践中发现,这意味着要么应用程序需要“创建”权限(一般来说,这不是一个好主意……通常,总是存在例外/排除),要么每次新实体/对象出现或部署时,都需要创建新的存储容器/表/集合。我们发现这对于本地开发来说非常缓慢,对于生产部署来说也有问题。你可能不会,但那是我们真实的经历

    另一件要记住的事情是
    Table Events
        AggregateId [Guid],
        Data [Blob],
        SequenceNumber [Long],
        Version [Int]
    
    CREATE TABLE [dbo].[EventFlow](
        [GlobalSequenceNumber] [bigint] IDENTITY(1,1) NOT NULL,
        [BatchId] [uniqueidentifier] NOT NULL,
        [AggregateId] [nvarchar](255) NOT NULL,
        [AggregateName] [nvarchar](255) NOT NULL,
        [Data] [nvarchar](max) NOT NULL,
        [Metadata] [nvarchar](max) NOT NULL,
        [AggregateSequenceNumber] [int] NOT NULL,
     CONSTRAINT [PK_EventFlow] PRIMARY KEY CLUSTERED 
    (
        [GlobalSequenceNumber] ASC
    )