2

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

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

3 回答 3

4

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

因此,您唯一需要做的就是向您的存储库添加一个映射到 EF方法的Find(key)or方法,然后您就完成了。GetByIdFind(key)

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

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

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

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

[PermittedRole(Role.LabManagement)]

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

更新

这是我认为您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;     
    }
}
于 2013-11-11T08:08:08.787 回答
1

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

验证

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

拆分命令处理程序

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

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

一般来说

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

于 2013-11-11T03:30:26.547 回答
0

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

于 2018-04-08T13:06:05.553 回答