发送命令时,CQRS事件采购检查用户名是唯一的或不是来自EventStore

som*_*eek 18 domain-driven-design unique cqrs event-sourcing

当我们拥有特定的唯一EntityID时,EventSourcing非常有效,但当我尝试从eventStore获取除特定EntityId以外的信息时,我很难过.

我正在使用CQS和EventSourcing.作为事件源的一部分,我们将事件存储在SQL表中作为列(EntityID(uniqueKey),EventType,EventObject(例如UserAdded)).

因此,在存储EventObject时,我们只是序列化DotNet对象并将其存储在SQL中,因此,所有与UserAdded事件相关的细节都将采用xml格式.我担心的是我想确保db中存在的userName应该是唯一的.

因此,在创建AddUser命令时,我必须查询EventStore(sql db)是否已在eventStore中存在特定的userName.因此,为此,我需要序列化事件存储中的所有UserAdded/UserEdited事件,并检查eventStore中是否存在请求的用户名.

但由于部分CQRS命令不允许查询,可能是因为Race条件.

所以,我尝试在发送AddUser命令之前只查询eventStore并通过序列化所有事件(UserAdded)获取所有UserNames并获取用户名,如果请求的用户名是唯一的,则命令其他方式抛出userName已经存在的异常.

与上述方法一样,我们需要查询整个数据库,并且每天可能有数十万个事件.因此,执行查询/反序列化将花费很多时间,这将导致性能问题.

我正在寻找任何更好的方法/建议维护用户名唯一通过从eventStore或任何其他方法获取所有userNames

Mat*_*att 12

因此,您的客户端(发出命令的东西)应该完全相信它发送的命令将被执行,并且必须通过确保在发送RegisterUserCommand之前没有其他用户注册该电子邮件地址来执行此操作. .换句话说,您的客户端必须执行验证,而不是您的域甚至是围绕域的应用程序服务.

来自http://cqrs.nu/Faq

这是一个常见的问题,因为我们明确没有在写入端执行交叉聚合操作.但是,我们有很多选择:

创建已分配用户名的读取端.当用户键入名称时,使客户端以交互方式查询读取端.

创建一个反应式传奇,以标记和禁用仍然使用重复用户名创建的帐户.(无论是极端巧合还是恶意还是因为客户有缺陷.)

如果最终的一致性对您来说不够快,可以考虑在写入端添加一个表,就像已经分配的名称那样添加一个小的本地读取端.使聚合事务包括插入该表.

  • 最后一段是务实的解决方案. (15认同)
  • 如果写入方是事件存储,则最后一个选项不可行 (2认同)
  • @AlexeyZimarev没有什么可以阻止你实现一个私有读模型,它只存在于不是事件存储的写模型中. (2认同)

ibe*_*dev 7

不禁止在写入操作中使用存储库查询不同的聚合作为业务逻辑的一部分。您可以通过使用某些域服务(跨聚合操作)来接受命令或由于重复用户而拒绝命令。Greg Young 在这里提到了这一点:https : //www.youtube.com/watch?v=LDW0QWie21s&t=24m55s

在正常情况下,您只需要查询所有UserCreated+UserEdited事件。如果您希望每天有数千个这样的事件,那么您的事件可能很臃肿,您应该更加原子地设计。例如,UserEdited每次用户发生某些事情时都会引发事件,请考虑使用UserPersonalDetailsEditedUserAccessInfoEdited或类似的方法,其中必须唯一的字段与其他用户字段的处理方式不同。这样,在接受或不接受命令之前查询所有UserCreated+UserAccessInfoEdited将是一个更轻松的操作。

我个人会采用以下方法:

  1. 事件中的更多原子性,以便更明确地描述涉及应该全局唯一的字段的所有内容(例如:UserCreatedUserAccessInfoEdited
  2. 在写入端有可用的投影,以便在写入操作期间查询它们。因此,例如,我会订阅 allUserCreatedUserAccessInfoEditedevents,以便保留一个包含所有唯一字段的可查询“表”(例如:电子邮件)。
  3. CreateUser命令到达域时,域服务将查询此电子邮件表并接受或拒绝该命令。

这个解决方案有点依赖于最终的一致性,并且有可能查询告诉我们该字段尚未被使用,并允许命令UserCreated在实际投影尚未从先前的事务中更新时成功引发事件,因此导致系统中有 2 个字段不是全局唯一的情况。

如果您想完全避免这些不确定情况,因为您的业务无法真正处理最终一致性,我的建议是在您的领域中通过将它们显式建模为无处不在的语言的一部分来处理此问题。例如,您可以对聚合进行不同的建模,因为很明显您的聚合 User 并不是您的交易边界(即:它取决于其他人)。


gui*_*e31 5

通常,没有正确的答案,只有适合您领域的答案。

您是否处于真正需要立即一致性的环境中?在通过查询(例如,在客户端)检查唯一性时和处理命令时之间创建相同用户名的几率有多大?例如,您的领域专家是否会容忍一百万分之一的用户名冲突(可以在事后补偿)?首先你会拥有一百万用户吗?

即使需要立即一致性,“用户名应该是唯一的”......在哪个范围内?A Company?一个OnlineStore?A GameServerInstance?您能否找到唯一性约束必须保持的最受限制的范围,并使该范围成为从中萌芽新用户的聚合根?如果聚合根使这些事件变得小而简单,为什么“重放所有 UserAdded/UserEdited 事件”解决方案会很糟糕?