Domain driven design 发送命令时,CQRS事件源检查用户名是否唯一,是否来自EventStore

Domain driven design 发送命令时,CQRS事件源检查用户名是否唯一,是否来自EventStore,domain-driven-design,unique,cqrs,event-sourcing,Domain Driven Design,Unique,Cqrs,Event Sourcing,当我们有特定的唯一EntityID时,EventSourcing工作得非常完美,但当我试图从eventStore而不是特定EntityID获取信息时,我遇到了困难 我将CQR与EventSourcing一起使用。作为事件源的一部分,我们将事件作为列(EntityID(uniqueKey)、EventType、EventObject(例如UserAdded))存储在SQL表中 因此,在存储EventObject时,我们只是序列化DotNet对象并将其存储在SQL中,因此,与UserAdded事件相

当我们有特定的唯一EntityID时,EventSourcing工作得非常完美,但当我试图从eventStore而不是特定EntityID获取信息时,我遇到了困难

我将CQR与EventSourcing一起使用。作为事件源的一部分,我们将事件作为列(EntityID(uniqueKey)、EventType、EventObject(例如UserAdded))存储在SQL表中

因此,在存储EventObject时,我们只是序列化DotNet对象并将其存储在SQL中,因此,与UserAdded事件相关的所有详细信息都将以xml格式显示。我关心的是我想确保数据库中的用户名是唯一的

因此,在发出AddUser命令时,我必须查询EventStore(sql db)中是否已存在特定的用户名。为此,我需要序列化事件存储中所有用户添加/用户编辑的事件,并检查请求的用户名是否存在于EventStore中

但作为CQRS的一部分,不允许查询命令可能是因为竞争条件。

因此,在发送AddUser命令之前,我尝试通过序列化所有事件(UserAdded)并获取用户名来查询eventStore并获取所有用户名,如果请求的用户名是唯一的,那么shoot命令else抛出用户名已经存在的异常

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

我正在寻找任何更好的方法/建议,通过从eventStore获取所有用户名或任何其他方法来保持用户名的唯一性

因此,您的客户机(发出命令的对象)应该完全相信它发送的命令会被执行,并且它必须确保:,在发送RegisterUserCommand之前,确保没有其他用户使用该电子邮件地址注册。换句话说,您的客户机必须执行验证,而不是您的域,甚至是围绕域的应用程序服务

这是一个经常发生的问题,因为我们没有明确地 在写端执行交叉聚合操作。是的, 但是,我们有许多选择:

创建已分配用户名的读取端。做客户 在用户键入名称时以交互方式查询读取端

创建一个反应性的传奇,以关闭和停用已关闭的帐户 但创建时使用了重复的用户名。(是否极端 巧合、恶意或由于有问题的客户。)

如果最终一致性不够快,请考虑添加 表位于写端,可以说是一个小型的本地读端 已分配名称。使聚合事务包括 插入到那张桌子里


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

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


即使需要即时一致性,“用户名也应该是唯一的”。。。在哪个范围内?A
公司
在线商店
?一个
GameServerInstance
?您能否找到唯一性约束必须包含的最受限制的作用域,并使该作用域成为聚合根,从中生成新用户?如果聚合根使这些事件变得小而简单,那么为什么“重播所有UserAdded/UserEdited事件”解决方案会很糟糕呢?

使用GetEventStore(来自Greg Young),您可以使用任何字符串作为aggregateId/StreamId。使用用户名作为聚合的id,而不是GUID,或者使用类似“mycompany.users.john”的组合作为键和。。瞧!你有免费的用户名唯一性

作为业务逻辑的一部分,在写操作中使用存储库查询不同的聚合是不被禁止的。通过使用某些域服务(交叉聚合操作),您可以这样做,以便接受命令或由于重复用户而拒绝命令。Greg Young在这里提到了这一点:

在正常情况下,您只需要查询所有
UserCreated
+
UserEdited
事件。 如果您希望每天有数千个这样的事件,那么可能您的事件太多了,您应该进行更原子化的设计。例如,在用户每次发生了一些事情时,使用< <代码>用户编辑的< /C>事件,考虑使用<代码> UsSerialDealStimeListEng/<代码>和用户访问Access(<代码> >或类似的内容,其中必须唯一的字段与用户字段的其余部分不同。这样,在接受或不接受命令之前查询所有
UserCreated
+
UserAccessInfoEdited
将是一个轻松的操作

就我个人而言,我会采用以下方法:

  • 事件中的原子性更强,因此所有涉及全局唯一字段的内容都会得到更明确的描述(例如:
    UserCreated
    UserAccessInfoEdited
  • 在写端有可用的投影,以便在写操作期间查询它们。例如,我会订阅所有
    UserCreated
    UserAccessInfoEdited
    事件,以便保留一个包含所有唯一字段(例如:email)的可查询“表”
  • CreateUser
    命令到达域时,域服务将查询此电子邮件表并接受或拒绝该命令
  • 这个解决方案有点依赖于c语言