命令中的 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' ,它就会产生不需要的所有额外数据的开销。
如果您想在执行存储库调用时共享代码,请使用称为规范模式和/或策略模式的东西。您不会遇到上面示例中任何令人讨厌的紧耦合问题,而且作为奖励,您的代码将更好地阅读,因为意图是最重要的而不是实现。
我对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' ,它就会产生不需要的所有额外数据的开销。
如果您想在执行存储库调用时共享代码,请使用称为规范模式和/或策略模式的东西。您不会遇到上面示例中任何令人讨厌的紧耦合问题,而且作为奖励,您的代码将更好地阅读,因为意图是最重要的而不是实现。