我有一个问题,我希望我的处理程序使用从处理程序生成的数据:
我的问题是架构和性能。
UpdateUserCommandHandlerAuthorizeDecorator
调用存储库(entityframework)来授权用户。我有其他类似的装饰器应该使用和修改实体并将其发送到链。
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();
}
}
答案 0 :(得分:4)
您不应仅为了提高性能而尝试传递任何额外数据。除了装饰器,你也无法改变合同。相反,您应该允许缓存该用户实体,这通常应该是存储库实现的责任。使用Entity Framework,这实际上非常简单。您可以调用DbSet.Find(id)
,EF将首先在缓存中查找实体。这可以防止不必要的往返数据库。我一直这样做。
因此,您唯一需要做的就是向您的存储库中添加Find(key)
或GetById
方法,以映射到EF的Find(key)
方法,您就完成了。
此外,我同意皮特的看法。装饰者应主要关注跨领域问题。在装饰器中添加其他东西有时候也可以,但是你似乎将处理器及其装饰器的核心业务逻辑分开了。将文件写入磁盘需要核心逻辑。您可能会对遵守单一责任感到遗憾,但在我看来,您将单一责任分配给多个班级。这并不意味着你的命令处理程序应该很大。正如Pete所说,您可能希望将其提取到服务中并将此服务注入到处理程序中。
验证授权是一个跨领域的问题,因此在装饰器中使用它似乎没问题,但是您当前的实现存在一些问题。首先,这样做将导致您拥有许多非通用装饰器,这会导致大量维护。此外,如果用户未经授权,您可以默默地跳过执行,这通常不是您想要的。
不要以静默方式跳过,而是考虑抛出异常并阻止用户在正常情况下调用此功能。这意味着如果抛出异常,则代码中存在错误,或者用户正在攻击您的系统。在没有抛出异常的情况下默默地跳过可能会使查找错误变得更加困难。
另一件事是您可能想要考虑尝试将此授权逻辑实现为通用装饰器。例如,有一个generc授权装饰器或验证装饰器。这可能并不总是可行,但您可以使用属性标记命令。例如,在我正在进行的系统中,我们标记了这样的命令:
[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;
}
}
答案 1 :(得分:1)
为什么要首先通过装饰器?
正常的方法是让客户端在提交命令之前执行任何所需的验证。创建/发布/执行的任何命令都应在提交之前执行所有(合理的)验证。我包括'合理',因为有些东西,如唯一性,不能事先100%验证。当然,执行命令的授权可以在提交之前完成。
让装饰器只处理命令处理逻辑的部分,然后丰富命令对象似乎对我来说过度工程。恕我直言,装饰者应该用于扩展具有附加功能的给定操作,例如记录,事务或身份验证(虽然我说过,但我认为这不适用于装饰命令处理程序)。
似乎上传图像,然后在数据库中分配新图像URL是一个命令处理程序的责任。如果您希望抽象出这两个不同操作的详细信息,那么请为您的处理程序注入类似IUserimageUploader
的类。
通常,命令被认为是不可变的,一旦创建就不应该更改。这有助于强制执行命令应预先包含完成操作所需的所有信息。
答案 2 :(得分:0)
我在这里有点晚了,但我要做的是定义一个可以IoC注入的IUserContext类。这样,您可以加载重要的用户数据一次,然后缓存它,所有其他依赖项可以使用相同的实例。然后,您可以让数据在这么久后过期,并且它会照顾好自己。