【问题标题】:How to model chat messages in an event-sourced system?如何在事件源系统中建模聊天消息?
【发布时间】:2021-02-24 04:17:55
【问题描述】:

上下文:我正在探索使用 EventStoreDB(每个聚合的单独事件流)和 Node.JS/TypeScript 构建一个事件源系统/PoC。该系统的一部分是一对一的客户支持聊天。创建聊天消息时,会向用户发送推送通知,其中包括对应用程序徽章编号的更新(未读消息总数)。我想知道对聚合/有界上下文建模的最佳方法是什么。

问题 1:聊天消息放在哪里?

问题2:如何处理客户的未读消息徽章计数器?

由于聊天消息本身就是已经计时的事件,它们看起来很容易适应事件源系统。尽管如此,我仍在寻找有关如何最好地为聚合建模的建议:

选项 A:由于每条聊天消息都有自己的生命周期(它们可以被编辑、具有可更新的阅读状态等),因此 ChatMessage 可以单独作为一个聚合。这会增加聚合(以及流)的数量,但这对于 EventStoreDB 来说可能并不是真正的问题。但是,要发送消息通知,我们需要知道未读消息的总数(因此有关其他聚合的信息)。但是发送“saga”/“流程管理器”(这是正确的术语?)的推送通知应该如何知道与通知一起发送的徽章计数器?它是否应该根据它所看到的所有事件为每个客户保留自己的状态/读取模型和当前计数器?

选项 B:另一种方法可能是在 Customer 聚合根目录下有一个消息列表。这样,客户可以有一个未读消息数量的计数器,并且所有事件的折叠都会给我这个数字。但是,这里恐怕客户聚合根的大量聊天消息事件会妨碍“简单”客户行为。例如。在处理客户命令时,我们首先通过折叠所有事件来获取当前状态(假设没有使用快照),这意味着应用所有这些聊天事件,甚至只是用客户的当前姓名做某事。

选项 C:还是应该在不同的有界上下文中?那么让客户在有界上下文中拥有其联系方式,并有一个单独的有界上下文用于聊天(或一般的通信),其中两者都有一个客户聚合根仅共享客户的 UUID?这会是两全其美,还是会带来其他挑战?

是否有任何选择?或者还有其他更好的选择吗?或者我只是完全忽略了这一点;)(不想排除)

非常感谢任何建议!

【问题讨论】:

    标签: domain-driven-design event-sourcing


    【解决方案1】:

    事件溯源描述了一种(重新)创建状态的方法,通过将每个更改存储为一个事件。这不包括这些事件是如何被持久化或快照的,或者它们是如何被读取和分发的。

    我总是从用户界面开始。因为在那里您应该知道要显示哪些信息以及可以执行哪些操作。

    例如,可能有以下命令(或用户界面执行的操作):

    • SendMessage(receiverId, content)

    • MarkMessageAsRead(messageId)

    然后您的服务器将检查提供的数据是否有效并创建相关事件:

    class SupportChatMessageAggregate {
      MessageId messageId;
      UserId senderId;
      UserId receiverId;
      String content;
      boolean readByReceiver;
    
      // depending on framework and personal preference, this could
      // also be a method: handle(SendMessage command, CurrentUser currentUser)
      constructor(SendMessage command, CurrentUser currentUser) {
        validate(command); // throws Exception if invalid
        // for example if content is empty,
        // or if currentUser is not allowed to send messages to receiverId
    
        publishEvent(new MessageSentEvent(
          command.getMessageId(),
          currentUser.getUserId(),
          command.getReceiverId(),
          command.getContent()
        ));
      }
    
      handle(MarkMessageAsRead command, CurrentUser currentUser) {
        validate(command); // throws Exception if invalid
        // for example check if currentUser == receiver
    
        publishEvent(new MessageMarkedAsReadEvent(
          command.getMessageId(),
          currentUser.getUserId()
        ));
      }
    
      ...
    
    }
    

    现在,当您想知道用户的徽章计数器时,只需将所有接收者 = currentUser 的 MessageSentEvents 相加,然后减去 currentUser 的所有 MessageMarkedAsReadEvents。

    这可以在 UnreadSupportChatMessageCountAggregate 中完成,它负责根据给定用户的 MessageSentEvents 和 MessageMarkedAsReadEvents 提供当前的 unreadMessages 值。一个非常无聊的聚合,但它可以完成工作。

    这就是事件溯源:你只是有一堆事件,如果你想查询一些数据,你只需要获取所有相关的事件,处理它们,然后得到你的结果。如果您对每个聚合使用单独的事件流,或者所有事件只有一个流是实现细节(或取决于您使用的事件存储)。

    根据事件的数量,这可能非常快,也可能非常慢。这就是快照和/或读取模型(来自 CQRS)派上用场的地方。但对于普通的事件溯源,这不是必需的。

    【讨论】:

      猜你喜欢
      • 2020-01-16
      • 1970-01-01
      • 2013-02-02
      • 2017-09-01
      • 2021-02-05
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2022-10-26
      相关资源
      最近更新 更多