CQRS代码在命令中重复

时间:2015-01-05 11:28:01

标签: repository dry cqrs duplication

我对CQRS原则的命令方面的代码重复有疑问。

一直关注以下文章:

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

在我看来,这种在自己的类中分离每个命令的方法在从数据存储中检索实体时会给出一些代码重复。

或许有点做作,但是让我们说例如我有一个命令,我希望重置用户密码给出他的电子邮件地址和我要更新用户的最后一个命令 登录日期。

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();
   }
}

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

我当然可以创建一个UserRepository,例如,它有一个GetUserByEmailAddress方法,并将IUserRepository插入我的CommandHandlers的构造函数中。但是,这最终会不会创建一个包含Save,GetById,GetByUsername等的“神存储库”?

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

如何保持此代码DRY?

3 个答案:

答案 0 :(得分:3)

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

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();
   }
}

这样,您可以在命令处理程序中重构私有帮助器方法,命令处理程序可以处理类似的命令,并减少代码重复。你也不会需要上帝&#34;库中。

就个人而言,我宁愿将私有GetUserByEmail帮助器作为单独的查询类,我通过构造函数注入db上下文,以便GetUserByEmail非常获得User的特定课程。

希望这有帮助。

答案 1 :(得分:1)

它不会创建神存储库。它将是一个具有命令用例使用的方法的适当的存储库。但这意味着您正在使用正确的存储库模式,这意味着没有IQueryable或EF暴露。请记住,域存储库只有域用例需要“查询”方法,而存储库只处理域聚合根(整个域对象)。

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

如果您的应用程序足够简单或者您知道将来不会有太大变化,那么您可以采用直接使用EF的快捷方式,但如果您决定以这种方式简化,那么您不需要CQRS和基于消息的架构。

答案 2 :(得分:1)

我知道它看起来像代码重复,我知道你看起来像是在反对DRY主体,但我可以向你保证在行为中使用共享服务存储库代码永远不是一个好主意。其中一个问题是每个行为实际上可能需要通过电子邮件调用略微不同的GetUser实现。一个我需要全名,另一个可能没有。通过共享此代码,您可以有效地将两个调用紧密耦合。一种行为现在可能需要返回额外的数据,但现在,当您正在紧密耦合时,您可以在其他实现调用此共享服务时进行呼叫。它具有它不需要的所有额外数据的开销。

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