命令中的 CQRS 代码重复

CQRS code duplication in commands

我对CQRS原理的命令端代码重复有疑问

一直关注以下文章:

https://www.cuttingedge.it/blogs/steven/pivot/entry.php?id=91 https://www.cuttingedge.it/blogs/steven/pivot/entry.php?id=92

在我看来,这种将每个命令分开的方法 class 在从数据存储中检索实体时会产生一些代码重复。

也许有点做作,但让我们举个例子,我有一个命令,我想在给定他的电子邮件地址的情况下重置用户密码,还有一个命令,我想更新用户的最后一个 登录日期。

public class ResetPasswordCommandHandler : CommandHandler<ResetPasswordCommand>
{
    public override void Execute(ResetPasswordCommand command)
    {
       **// duplication here**
        var user = from c in db.Users
            where c.EmailAddress = command.EmailAddress
            select c;

        user.EmailAddress = command.EmailAddress;
        ...
        db.Save();
   }
}

public class UpdateLastLoginCommandHandler : CommandHandler<UpdateLastLoginCommand>
{
    public override void Execute(UpdateLastLoginCommand command)
    {
       **// duplication here**
        var user = from c in db.Users
            where c.EmailAddress = command.EmailAddress
            select c;

        user.LastLogin = DateTime.Now;
        ...
        db.Save();
   }
}

在这两个命令中,我都根据用户的电子邮件地址检索用户。现在,如果我想在查询数据库之前 trim 输入 UI,我将不得不在两个地方进行更改。

我当然可以创建一个 UserRepository,例如,它有一个方法 GetUserByEmailAddress 并将该 IUserRepository 插入到我的 CommandHandlers 的构造函数中。但是,这最终不会创建一个带有 Save、GetById、GetByUsername 等的 "god repository" 吗?

如果我创建一个存储库,为什么要创建单独的查询对象?

如何让这段代码保持干爽?

为什么不将您的命令处理程序重构为实现多个接口的命令处理程序:

public class UserCommandHandler : CommandHandlerBase, 
                                  IHandle<ResetPasswordCommand>,
                                  IHandle<UpdateLastLoginCommand>
{
    public void Execute(ResetPasswordCommand command)
    {
        var user = GetUserByEmail(command.EmailAddress);

        user.EmailAddress = command.EmailAddress;
        ...
        db.Save();
   }

    public void Execute(UpdateLastLoginCommand command)
    {
        var user = GetUserByEmail(command.EmailAddress);

        user.LastLogin = DateTime.Now;
        ...
        db.Save();
   }

   private User GetUserByEmail(string email) {
            return (from c in db.Users
                   where c.EmailAddress = command.EmailAddress
                   select c).FirstOrDefault();
   }
}

这样,您可以在命令处理程序中重构私有辅助方法,您的命令处理程序可以处理类似的命令,并且可以减少代码重复。您也不需要 "god" 存储库。

就个人而言,我宁愿将私有 GetUserByEmail 助手作为单独的查询 class,我通过构造函数将 db 上下文注入其中,以便 GetUserByEmail 是一个非常具体的 class 让我得到一个 User.

希望这对您有所帮助。

它不会创建上帝存储库。它将是一个适当的存储库,具有命令用例使用的方法。但这意味着您使用的是正确的存储库模式,这意味着没有公开 IQueryable 或 EF。请记住,域存储库具有 'query' 仅域用例所需的方法,并且存储库仅处理域聚合根(整个域对象)。

您当前的方法是在您的应用程序服务中使用 EF(最后是 DAO),这意味着应用程序层和可能的域层都与 EF 耦合。您的高级服务(命令处理程序最终是一项服务,实现用例)不应该进行查询,因为从他们的角度来看,没有 rdbms,即他们不知道查询。

如果您的应用足够简单,或者您知道它在未来不会有太大变化,那么您可以选择直接使用 EF 的捷径,但如果您决定以这种方式简化事情,那么您就不必这样做需要 CQRS 和基于消息的架构。

我知道这看起来像是代码重复,我知道这看起来像是在违背 DRY 原则,但我可以向您保证,在行为中使用共享服务存储库代码绝不是一个好主意。问题之一是每种行为实际上可能需要通过电子邮件调用对 GetUser 的实现略有不同。一个我需要全名,另一个可能不需要。通过共享此代码,您可以有效地紧密耦合这两个调用。一种行为现在可能需要返回一条额外的数据,但现在当您紧密耦合时,只要其他实现调用此 'shared service' ,它就会产生不需要的所有额外数据的开销。

如果您想在执行存储库调用时共享代码,请使用称为规范模式和/或策略模式的东西。您不会遇到上面示例中任何令人讨厌的紧耦合问题,而且作为奖励,您的代码将更好地阅读,因为意图是最重要的而不是实现。