【问题标题】:CommandHandler decorators dependencyCommandHandler 装饰器依赖项
【发布时间】:2013-11-22 08:00:12
【问题描述】:

我有一个问题,我希望我的处理程序使用从处理程序生成的数据:

  1. UpdateUserProfileImageCommandHandlerAuthorizeDecorator
  2. UpdateUserProfileImageCommandHandlerUploadDecorator
  3. UpdateUserProfileImageCommandHandler

我的问题是架构和性能。

UpdateUserCommandHandlerAuthorizeDecorator 调用存储库(实体框架)以授权用户。我有其他类似的装饰器应该使用和修改实体并将其发送到链上。

UpdateUserCommandHandler 应该只是将用户保存到数据库中。我目前必须进行另一个存储库调用并更新实体,而我本可以使用以前的装饰器处理实体。

我的问题是该命令只接受用户 ID 和一些要更新的属性。在我从 Authorize 装饰器获取用户实体的情况下,我怎样才能在链上的实体上工作?是否可以将 User 属性添加到命令中并进行处理?

代码:

public class UpdateUserProfileImageCommand : Command
{
    public UpdateUserProfileImageCommand(Guid id, Stream image)
    {
        this.Id = id;
        this.Image = image;
    }

    public Stream Image { get; set; }

    public Uri ImageUri { get; set; }
}

public class UpdateUserProfileImageCommandHandlerAuthorizeDecorator : ICommandHandler<UpdateUserProfileImageCommand>
{
    public void Handle(UpdateUserProfileImageCommand command)
    {
         // I would like to use this entity in `UpdateUserProfileImageCommandHandlerUploadDecorator`
         var user = userRespository.Find(u => u.UserId == command.Id);

         if(userCanModify(user, currentPrincipal))
         {
             decoratedHandler(command);
         }

    }
}

public class UpdateUserProfileImageCommandHandlerUploadDecorator : ICommandHandler<UpdateUserProfileImageCommand>
{
    public void Handle(UpdateUserProfileImageCommand command)
    {
         // Instead of asking for this from the repository again, I'd like to reuse the entity from the previous decorator
         var user = userRespository.Find(u => u.UserId == command.Id);

         fileService.DeleteFile(user.ProfileImageUri);

         var command.ImageUri = fileService.Upload(generatedUri, command.Image);

         decoratedHandler(command);       

    }
}

public class UpdateUserProfileImageCommandHandler : ICommandHandler<UpdateUserProfileImageCommand>
{
    public void Handle(UpdateUserProfileImageCommand command)
    {
         // Again I'm asking for the user...
         var user = userRespository.Find(u => u.UserId == command.Id);

         user.ProfileImageUri = command.ImageUri;     

         // I actually have this in a PostCommit Decorator.
         unitOfWork.Save();
    }
}

【问题讨论】:

  • 您在问题中使用了“up the chain”两次。你这是什么意思?
  • @Steven,我认为装饰器类似于链接,1 个装饰器调用另一个装饰器,然后到达实际的命令处理程序。所以我希望数据从一个装饰器传递到另一个装饰器,直到处理程序
  • 您能否更新您的问题并举例说明您在做什么?
  • @Steven 我写了一些代码,其中包含一些关于我正在尝试做的事情。
  • @ShawnMclean 如果您使用 NHibernate 之类的东西,那么您应该能够依靠二级缓存来避免重复的数据库调用来查找用户。您是否担心已验证存在的性能问题?

标签: c# .net dependency-injection decorator cqrs


【解决方案1】:

您不应仅仅为了性能而尝试传递任何额外的数据。此外,使用装饰器,您不能更改合同。相反,您应该允许缓存该用户实体,这通常应该是存储库实现的责任。使用实体框架,这实际上相当简单。您可以调用DbSet.Find(id),EF 将首先在缓存中查找实体。这可以防止不必要的数据库往返。我一直这样做。

所以您唯一需要做的就是将Find(key)GetById 方法添加到映射到EF 的Find(key) 方法的存储库中,然后您就完成了。

此外,我同意皮特的观点。装饰器应该主要用于横切关注点。有时在装饰器中添加其他东西是可以的,但您似乎将核心业务逻辑拆分为处理程序及其装饰器。将文件写入磁盘需要核心逻辑。您可能对遵守单一职责感到担忧,但在我看来,您将单一职责拆分为多个类。这并不意味着您的命令处理程序应该很大。正如 Pete 所说,您可能希望将其提取到服务中并将此服务注入到处理程序中。

验证授权是一个跨领域的问题,因此在装饰器中使用它似乎没问题,但是您当前的实现存在一些问题。首先,这样做会导致你有很多非泛型的装饰器,从而导致大量的维护。此外,如果用户未经授权,您会默默地跳过执行,这通常不是您想要的。

与其默默跳过,不如考虑抛出异常并阻止用户在正常情况下调用此功能。这意味着如果抛出异常,则说明您的代码中存在错误,或者用户正在入侵您的系统。在不引发异常的情况下静默跳过会使查找错误变得更加困难。

另一件事是您可能要考虑尝试将此授权逻辑实现为通用装饰器。例如有一个通用授权装饰器或验证装饰器。这可能并不总是可行的,但您可以使用属性标记命令。例如,在我目前正在开发的系统中,我们将命令标记为:

[PermittedRole(Role.LabManagement)]

我们有一个AuthorizationVerifierCommandHandlerDecorator&lt;TCommand&gt;,它检查正在执行的命令的属性并验证当前用户是否被允许执行该命令。

更新

以下是我认为您的UpdateUserProfileImageCommandHandler 的示例:

public class UpdateUserProfileImageCommandHandler 
    : ICommandHandler<UpdateUserProfileImageCommand>
{
    private readonly IFileService fileService;

    public UpdateUserProfileImageCommandHandler(IFileService fileService)
    {
        this.fileService = fileService;
    }

    public void Handle(UpdateUserProfileImageCommand command)
    {
         var user = userRespository.GetById(command.Id);

         this.fileService.DeleteFile(user.ProfileImageUri);

         command.ImageUri = this.fileService.Upload(generatedUri, command.Image);

         user.ProfileImageUri = command.ImageUri;     
    }
}

【讨论】:

  • 非常感谢,您能否举一个将服务注入命令处理程序的示例?还是我已经通过构造函数注入的 fileService 这样做了?
  • 在这种情况下,您如何告诉 AuthorizationVerifierCommandHandlerDecorator 当前用户是什么以便它进行检查?
  • @RhysW:通过将IUserContext 注入其构造函数。它的实现可以从 Active Directory 或 HttpContext.Current 或您决定管理用户角色的任何其他方式加载当前用户身份及其角色。
  • @Steven 感谢您的快速回复。我被困在这一点上,因为我的每个面向外部的 api 方法都接受名称、密钥,然后是相关数据。相关数据是我试图传递给我的命令处理程序的内容,但授权装饰器需要这两个参数的内容。鉴于它的一个管道,我看不到传递两个不同数据的方法。您知道我可以在哪些地方找到有关我的示例的文档吗?
  • @RhysW:我不知道这样的例子。尝试在 SO 上发布一个新问题,并提供适当的详细信息,尤其是对这些数据的描述、它们是什么以及它们是如何提供的。
【解决方案2】:

为什么首先要通过装饰器来做到这一点?

验证

通常的方法是让客户端在提交命令之前进行所有所需的验证。创建/发布/执行的任何命令都应该在提交之前执行所有(合理的)验证。我包括“合理”是因为有些东西,比如独特性,不能事先得到 100% 的验证。当然,执行命令的授权可以在提交之前完成。

拆分命令处理程序

拥有一个只处理命令处理逻辑的部分的装饰器,然后丰富命令对象对我来说似乎是过度设计。恕我直言,装饰器应该用于扩展具有附加功能的给定操作,例如日志记录、事务或身份验证(尽管就像我说的,我认为这不适用于装饰命令处理程序)。

似乎上传图像,然后在数据库中分配新的图像 URL 是一个命令处理程序的责任。如果您希望这两个不同操作的详细信息被抽象出来,那么将您的处理程序注入执行此操作的类,例如 IUserimageUploader

一般

通常,命令被认为是不可变的,一旦创建就不应更改。这是为了帮助强制执行命令应预先包含完成操作所需的所有必要信息。

【讨论】:

  • 我试图遵循单一责任原则,但单一的命令处理程序会让这更容易
  • 是的,我可以看到 SRP 参数。另一种方法是两阶段 UploadUserImageCommand -> UploadUserImageCommandHandler -> 执行新的 AssignUpdatedUserImageUrlCommand(userId, url) -> AssignUpdatedUserImageUrlCommandHandler。
【解决方案3】:

我在这里有点晚了,但我要做的是定义一个可以 IoC 注入的 IUserContext 类。这样,您可以一次加载重要的用户数据,然后将其缓存,并且所有其他依赖项都可以使用相同的实例。然后,您可以让该数据在很长一段时间后过期,它会自行处理。

【讨论】:

    猜你喜欢
    • 2016-10-20
    • 1970-01-01
    • 2021-01-05
    • 2013-04-08
    • 2021-10-14
    • 2021-05-15
    • 2016-07-26
    • 2016-02-08
    • 1970-01-01
    相关资源
    最近更新 更多