首页 文章

使用RDBMS作为事件源存储

提问于
浏览
91

如果我使用RDBMS(例如SQL Server)来存储事件源数据,架构可能是什么样的?

我从抽象的意义上看到了一些变化,但没有具体的 .

例如,假设有一个“产品”实体,对该产品的更改可以采用以下形式:价格,成本和描述 . 我很困惑我是否:

  • 有一个"ProductEvent"表,其中包含产品的所有字段,其中每个更改表示该表中的新记录,并在适当时加上"who, what, where, why, when and how" . 当成本,价格或描述发生变化时,会添加一个全新行来表示产品 .

  • 将产品成本,价格和描述存储在使用外键关系连接到Product表的单独表中 . 当发生对这些属性的更改时,请根据需要使用WWWWWH写入新行 .

  • 在"ProductEvent"表中存储WWWWWH以及表示事件的序列化对象,这意味着必须在我的应用程序代码中加载,反序列化并重新播放事件本身,以便为给定产品重新构建应用程序状态 .

特别是我担心上面的选项2 . 极端情况下,产品表几乎是每个表一个表,在这里加载给定产品的应用程序状态需要从每个产品事件表加载该产品的所有事件 . 这桌爆炸对我来说有点不对劲 .

我确信“这取决于”,虽然没有单一的“正确答案”,但我试图了解什么是可以接受的,什么是完全不可接受的 . 我也知道NoSQL可以在这里提供帮助,其中事件可以存储在聚合根目录中,这意味着只有一个请求数据库来获取事件来重建对象,但是我们没有使用NoSQL数据库 . 那一刻所以我感觉到了另类 .

4 回答

  • 3

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

    以下是Ncqrs中使用的架构 . 如您所见,表"Events"将相关数据存储为CLOB(即JSON或XML) . 这对应于您的选项3(只有没有"ProductEvents"表,因为您只需要一个通用的"Events"表 . 在Ncqrs中,到您的聚合根的映射通过"EventSources"表发生,其中每个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's Event Store implementation的SQL持久性机制基本上由一个名为"Commits"的表组成,其中一个BLOB字段为"Payload" . 这与Ncqrs几乎相同,只是它以二进制格式序列化事件的属性(例如,添加加密支持) .

    Greg Young建议采用类似的方法,如extensively documented on Greg's website .

    他的典型“事件”表的架构如下:

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

    GitHub项目CQRS.NET有几个具体的例子说明如何使用几种不同的技术来完成EventStores . 在撰写本文时,在SQL using Linq2SQL中有一个实现,并且SQL schema与它一起使用,其中一个用于MongoDB,一个用于DocumentDB(CosmosDB,如果你在Azure中)和一个使用EventStore(如上所述) . Azure中的更多内容,如表存储和Blob存储,与平面文件存储非常相似 .

    我想这里的要点是它们都符合相同的委托人/ Contract . 它们都将信息存储在一个地方/容器/表中,它们使用元数据来识别来自另一个事件的一个事件,并且“只是”按原样存储整个事件 - 在某些情况下序列化,支持技术,就像它一样 . 因此,根据您选择文档数据库,关系数据库甚至平面文件,有几种不同的方式可以达到事件存储的相同意图(如果您在任何时候改变主意并发现需要迁移或支持,这将非常有用不止一种存储技术) .

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

    首先我们发现(即使使用唯一的UUID / GUID而不是整数)由于多种原因出于战略原因而出现顺序ID,因此只有ID对于密钥而言不够独特,因此我们将主ID密钥列与数据/合并对象类型创建应该是真正的(在您的应用程序意义上)唯一键 . 我知道有些人说你不需要存储它,但这取决于你是否是绿地还是必须与现有系统共存 .

    出于可维护性的原因,我们坚持使用单个容器/表/集合,但我们确实为每个实体/对象使用了一个单独的表 . 我们在实践中发现,这意味着应用程序需要“创建”权限(通常说这不是一个好主意...通常,总是有异常/排除)或每次新实体/对象出现或部署时,新存储需要制作容器/ table /收藏品 . 我们发现这对于本地开发来说非常缓慢,并且对于 生产环境 部署来说也存在问题 . 你可能没有,但那是我们的真实体验 .

    要记住的另一件事是,要求操作X发生可能会导致发生许多不同的事件,从而了解命令/事件/有用的所有事件 . 它们也可以跨越不同的对象类型,例如在购物车中按"buy"可能会触发帐户和仓储事件 . 消费应用程序可能想知道所有这些,因此我们添加了CorrelationId . 这意味着消费者可以询问因其请求而提出的所有事件 . 你会在schema看到它 .

    特别是使用SQL,我们发现如果索引和分区不是't adequately used. Remember events will needs to be streamed in reverse order if you are using snapshots. We tried a few different indexes and found that in practise, some additional indexes were needed for debugging in-production real-world applications. Again you',那么性能确实成了瓶颈 .

    其他 生产环境 中的元数据在基于 生产环境 的调查中很有用,时间戳让我们深入了解事件持续存在与提升的顺序 . 这为我们提供了一些特别重要的事件驱动系统的帮助,该系统引发了大量事件,为我们提供了有关网络和整个网络系统分布等性能的信息 .

  • 93

    好吧,你可能想看看Datomic .

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

    我写了详细的答案here

    您可以观看Stuart Halloway的演讲,解释Datomic的设计here

    由于Datomic及时存储事实,您可以将其用于事件采购用例等等 .

  • 1

    可能的提示是设计后跟“慢慢改变尺寸”(类型= 2)应该可以帮助您覆盖:

    • 事件发生的顺序(通过代理键)

    • 每个州的持久性(有效期从 - 有效)

    左折叠功能也应该可以实现,但您需要考虑未来的查询复杂性 .

相关问题