【问题标题】:Where should I put a logic for querying extra data in CQRS command flow我应该在哪里放置用于在 CQRS 命令流中查询额外数据的逻辑
【发布时间】:2016-06-20 15:20:19
【问题描述】:

我目前正在尝试实现没有事件溯源的简单 DDD/CQRS 架构。

目前我需要编写一些代码来向文档实体添加通知(文档可以有多个通知)。

我已经创建了一个命令 NotificationAddCommand、ICommandService 和 IRepository。

在通过 IRepository 插入新通知之前,我必须使用 NotificationAddCommand.User_name 属性从 db 查询当前 user_id。

我不知道该怎么做,因为我可以

  1. 使用读取流中的IQuery
  2. 将 user_name 传递给域实体并在存储库中解析 user_id。

代码:

public class DocumentsCommandService : ICommandService<NotificationAddCommand>
{
    private readonly IRepository<Notification, long> _notificationsRepository;

    public DocumentsCommandService(
        IRepository<Notification, long> notifsRepo)
    {
        _notificationsRepository = notifsRepo;
    }

    public void Handle(NotificationAddCommand command)
    {
        // command.user_id = Resolve(command.user_name) ??
        // command.source_secret_id = Resolve(command.source_id, command.source_type) ??
            foreach (var receiverId in command.Receivers)
            {
                var notificationEntity = _notificationsRepository.Get(0);
                notificationEntity.TargetId = receiverId;
                notificationEntity.Body = command.Text;
                _notificationsRepository.Add(notificationEntity);
            }            
    }
}

如果我在插入之前需要更复杂的逻辑怎么办?可以使用 IQuery 还是应该创建其他服务?

【问题讨论】:

  • 命令处理程序应该注入另一个专门的服务。顺便说一句,你的代码很奇怪。我敢肯定,当他们描述“添加通知”用例时,它完全没有反映领域专家的语言。无论如何,即使“添加”通知也没有任何意义……通知可以让您了解发生的事情,例如事件。你想改写历史吗?
  • 通知就像一条关于某事的消息,它包含notificationBody和notificationTargetUserId属性

标签: asp.net entity-framework domain-driven-design repository-pattern cqrs


【解决方案1】:

重用您的IQuery 的想法在某种程度上违背了 CQRS 的目的,因为您的读取端应该针对拉数据进行优化以用于显示/查询目的 - 这意味着它可以被非规范化、分布等您认为必要的任何方式 不受命令端的限制或影响(一个关键示例是它可能不会立即保持一致,而您的命令端显然需要完整性/有效性目的)。

考虑到这一点,您应该考虑为您的写入方实施一个合同,该合同将为您解决必要的信息。从消费者出发,可能看起来像这样:

public DocumentsCommandService(IRepository<Notification, long> notifsRepo,
                               IUserIdResolver userIdResolver)

public interface IUserIdResolver
{
    string ByName(string username);
}

适当地实施IUserIdResolver

当然,如果这和查询端都使用相同的低级数据访问实现(例如,立即一致的存储库),那很好 - 重要的是您的架构是这样的,如果您需要换出哪里您的读取方出于以下目的获取其数据,例如促进缓慢的离线过程,您的读取和写入端充分分离,您可以交换您正在读取的位置,而无需解开读取与写入的纠缠。

最终,最重要的是了解为什么你要在你的场景中做出你正在做出的架构决策 - 然后你会发现做这些更容易以一种或另一种方式做出各种决定。

【讨论】:

  • 我明白你的意思,同意读写双方应该分开。
  • @Batavia 据我了解,IRepository 不应包含通用查询方法,而应仅包含 get/update/insert/remove 聚合根方法。所以可能任何异常逻辑都应该放在相应的小服务中(IUserIdResolver 等)
  • 你没有包含通用查询方法是对的,但这就是为什么你要创建一个非常具体的存储库,其中包含一个非常具体的查询方法,它没有在你的通用界面上公开,而只是在你的实现中
【解决方案2】:

在我正在工作的项目中,我遇到了类似的问题。我看到了解决这个问题的 3 个选项

1) 我所做的是创建一个具有查询选项的 UserCommandRepository。然后你会将该存储库注入到你的服务中。

由于我确实需要的几个查询非常简单(仅返回单个值),因此在我的情况下似乎是一个很好的权衡。

2) 另一种处理方法是强制用户只使用 user_id 发出命令。然后你就可以让他做查询了。

3) 第三种选择是问自己为什么需要 user_id。如果要在查询数据时建立一些关系,您也可以在查询数据(或将 writeDB 传播到 readDB 时)使用此句柄

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-02-18
    • 1970-01-01
    • 2015-11-03
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多