CommandHandler装饰器依赖性 [英] CommandHandler decorators dependency

查看:68
本文介绍了CommandHandler装饰器依赖性的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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


  1. UpdateUserProfileImageCommandCommandHandlerAuthorizeDecorator

  2. UpdateUserProfileImageCommandHandlerUploadDecorator

  3. UpdateUserProfileImageCommandHandler

我的问题是体系结构和性能。 / p>

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



UpdateUserCommandHandler 应该只需将用户保存到数据库即可。我现在必须再次调用存储库并更新实体,而我本可以从以前的装饰器处理实体。



我的问题是该命令仅接受用户ID和一些要更新的属性。如果我从Authorize装饰器获取用户实体,那么我仍如何在链上的那个实体上工作?是否可以在命令中添加 User 属性并对其进行处理?



代码:

 公共类UpdateUserProfileImageCommand:命令
{
public UpdateUserProfileImageCommand(Guid id,Stream image)
{
this.Id = id;
this.Image =图片;
}

public Stream Image {get;组; }

公共Uri ImageUri {get;组; }
}

公共类UpdateUserProfileImageCommandHandlerAuthorizeDecorator:ICommandHandler< UpdateUserProfileImageCommand>
{
public void Handle(UpdateUserProfileImageCommand命令)
{
//我想在ʻUpdateUserProfileImageCommandCommandHandlerUploadDecorator`中使用该实体
var user = userRespository.Find(u => u.UserId == command.Id);

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

}
}

公共类UpdateUserProfileImageCommandHandlerHandlerUploadDecorator:ICommandHandler< UpdateUserProfileImageCommand>
{
public void Handle(UpdateUserProfileImageCommand命令)
{
//我不想重用存储库中的内容,而是要重用先前装饰器中的实体
var user = userRespository.Find(u => u.UserId == command.Id);

fileService.DeleteFile(user.ProfileImageUri);

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

decorateHandler(command);

}
}

公共类UpdateUserProfileImageCommandCommandHandler:ICommandHandler< UpdateUserProfileImageCommand>
{
public void Handle(UpdateUserProfileImageCommand命令)
{
//再次询问用户...
var user = userRespository.Find(u => u.UserId == command.Id);

user.ProfileImageUri = command.ImageUri;

//我实际上在PostCommit装饰器中有此功能。
unitOfWork.Save();
}
}


解决方案

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



因此,您唯一要做的就是添加 Find(key)或将 GetById 方法复制到与EF的 Find(key)方法相对应的存储库中。



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



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



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



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

  [PermittedRole(Role.LabManagement) ] 

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



UPDATE



下面是我认为您的 UpdateUserProfileImageCommandCommandHandler 的示例:

 公共类UpdateUserProfileImageCommandCommandHandler 
:ICommandHandler< UpdateUserProfileImageCommand>
{
私有只读IFileService fileService;

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

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

this.fileService.DeleteFile(user.ProfileImageUri);

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

user.ProfileImageUri = command.ImageUri;
}
}


I have an issue where I would like my handler to use data generated from the handlers:

  1. UpdateUserProfileImageCommandHandlerAuthorizeDecorator
  2. UpdateUserProfileImageCommandHandlerUploadDecorator
  3. UpdateUserProfileImageCommandHandler

My problem is both architectural and performance.

UpdateUserCommandHandlerAuthorizeDecorator makes a call to the repository (entityframework) to authorize the user. I have other decorators similar to this that should use and modify entities and send it up the chain.

UpdateUserCommandHandler should just save the user to the database. I currently have to make another repository call and update the entity while I could have worked on the entity from the previous decorator.

My issue is that the command only accepts the user Id and some properties to update. In the case where I get the user entity from the Authorize decorator, how can I still work on that entity up the chain? Is it Ok to add that User property to the command and work on that?

Code:

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

解决方案

You should not try to pass on any extra data just for the sake of performance. Besides, usng decorators, you can't change the contract. Instead you should allow that user entity to be cached and this should typically be the responsibility of the repository implementation. With Entity Framework this is actually rather straightforward. You can call DbSet.Find(id) and EF will first look up the entity in the cache. This prevents unneeded round trips to the database. I do this all the time.

So the only thing you have to do is add a Find(key) or GetById method to your repository that maps to EF's Find(key) method and you're done.

Furthermore, I agree with Pete. Decorators should be primarily for cross-cutting concerns. Adding other things in decorators can be okay sometimes, but you seem to split up the core business logic over both the handler and its decorators. Writing the file to disk be longs to the core logic. You might be conserned about adhering to the Single Responsibility, but it seems to me that your splitting a single responsibility out over multiple classes. That doesn't mean that your command handlers should be big. As Pete said, you probably want to extract this to a service and inject this service into the handler.

Validating the authorization is a cross-cutting concern, so having this in a decorator seems okay, but there are a few problems with your current implementation. First of all, doing it like this will cause you to have many non-generic decorators, which leads to a lot of maintenance. Besides, you silently skip the execution if the user is unauthorized which is typically not what you want.

Instead of silently skipping, consider throwing an exception and prevent the user from being able to call this functionality under normal circumstances. This means that if an exception is thrown, there's either a bug in your code, or the user is hacking your system. Silently skipping without throwing an exception can make it much harder to find bugs.

Another thing is that you might want to consider is trying to implement this authorization logic as generic decorator. For instance have a generc authorization decorator or validation decorator. This might not always be possible, but you might be able to mark commands with an attribute. For instance, in the system I'm currently working on we mark our commands like this:

[PermittedRole(Role.LabManagement)]

We have a AuthorizationVerifierCommandHandlerDecorator<TCommand> that checks the attributes of the command being executed and verifies whether the current user is allowed to execute that command.

UPDATE

Here's an example of what I think your UpdateUserProfileImageCommandHandler could look like:

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

这篇关于CommandHandler装饰器依赖性的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆