2013-11-10 2 views
2

У меня есть вопрос, где я хотел бы мой обработчик, чтобы использовать данные, полученные из обработчиков:CommandHandler декораторы зависимость

  1. UpdateUserProfileImageCommandHandlerAuthorizeDecorator
  2. UpdateUserProfileImageCommandHandlerUploadDecorator
  3. UpdateUserProfileImageCommandHandler

Моя проблема в том, как архитектурный и производительность.

UpdateUserCommandHandlerAuthorizeDecorator делает звонок в репозиторий (сущность) для авторизации пользователя. У меня есть другие декораторы, подобные этому, которые должны использовать и модифицировать объекты и отправлять их цепочке.

UpdateUserCommandHandler должен просто сохранить пользователя в базе данных. В настоящее время я должен сделать другой вызов репозитория и обновить объект, пока я мог бы работать с сущностью из предыдущего декоратора.

Моя проблема заключается в том, что команда принимает только идентификатор пользователя и некоторые свойства для обновления. В случае, когда я получаю пользовательский объект от Authorize decorator, как я могу по-прежнему работать над этой сущностью в цепочке? Можно ли добавить это свойство 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

Вы можете использовать «по цепочке» дважды в вашем вопросе. Что ты имеешь в виду? – Steven

+0

@Steven, я считаю декораторы чем-то вроде цепочки, 1 декоратор вызывает другого, переходя к фактическому обработчику команд. Поэтому мне бы хотелось, чтобы данные передавались от одного декоратора к другому до обработчика –

+0

Не могли бы вы обновить свой вопрос и привести пример того, что вы делаете? – Steven

ответ

4

Вы не должны пытаться передавать какие-либо дополнительные данные только ради производительности. Кроме того, декодеры usng, вы не можете изменить контракт. Вместо этого вы должны разрешить кэширование этого пользовательского объекта, и это, как правило, несет ответственность за реализацию репозитория. С Entity Framework это на самом деле довольно просто. Вы можете позвонить DbSet.Find(id), а EF сначала будет искать объект в кеше. Это предотвращает ненужные круглые поездки в базу данных. Я все время это делаю.

Так что единственное, что вам нужно сделать, это добавить Find(key) или GetById метод в репозиторий, который отображается Find(key) метод EF и вы сделали.

Кроме того, я согласен с Пит. Декораторы должны быть в первую очередь для сквозных проблем. Иногда добавление других вещей в декораторы может быть хорошо, но вы, похоже, разделяете основную бизнес-логику как с обработчиком, так и с его декораторами. Написание файла на диск является долгой для основной логики. Вы можете быть уверены в соблюдении единой ответственности, но мне кажется, что вы разделили одну ответственность за несколько классов. Это не значит, что ваши обработчики команд должны быть большими. Как сказал Пит, вы, вероятно, захотите извлечь это в службу и вставить эту службу в обработчик.

Подтверждение авторизации - это сквозная проблема, поэтому наличие этого в декораторе кажется все в порядке, но есть несколько проблем с вашей текущей реализацией. Прежде всего, делать это так, это приведет к тому, что у вас будет много нестандартных декораторов, что приведет к большому количеству обслуживания. Кроме того, вы молча пропускаете выполнение, если пользователь несанкционирован, что обычно не то, что вы хотите.

Вместо бесшумного пропуска, рассмотрите возможность исключения исключения и не позволяйте пользователю использовать эту функцию при нормальных обстоятельствах. Это означает, что если генерируется исключение, в вашем коде есть ошибка, или пользователь взламывает вашу систему. Бесшумное пропущение без исключения исключений может затруднить поиск ошибок.

Другое дело, что вы, возможно, захотите рассмотреть, пытается реализовать эту авторизационную логику в качестве универсального декоратора. Например, у вас есть декодер авторизации генератора или декоратор проверки. Это может быть не всегда возможно, но вы можете отмечать команды атрибутом. Например, в системе, которую я сейчас работаю над мы отмечаем наши команды, как это:

[PermittedRole(Role.LabManagement)] 

У нас есть AuthorizationVerifierCommandHandlerDecorator<TCommand>, который проверяет атрибуты выполняемой команды и проверяет текущий пользователь имеет право выполнять ли что команда.

UPDATE

Вот пример того, что я думаю, что ваш 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;  
    } 
} 
+0

Большое спасибо, не могли бы вы привести пример ввода службы в обработчик команд? Или я уже делаю это с файловым сервисом, который вводится через конструктор? –

+1

@ShawnMclean: Смотрите мое обновление. – Steven

1

Почему это с помощью декораторов в первую очередь?

Validation

Нормальный подход должен иметь клиентов делать любые проверки требуется перед тем подачи команды. Любая команда, которая создается/публикуется/выполняется, должна иметь все (разумную) проверку, выполненную перед отправкой. Я включаю «разумные», потому что есть некоторые вещи, такие как уникальность, которые не могут быть проверены на 100% заранее. Конечно, авторизация для выполнения команды может быть выполнена перед отправкой.

Split команды Обработчики

Имея декоратор, который обрабатывает только части в логике обработки команды, а затем обогащает объект команды кажется, более-инженерный мне. IMHO, декораторы должны использоваться для расширения данной операции с дополнительной функциональностью, например. регистрации, транзакций или аутентификации (хотя, как я уже сказал, я не думаю, что это относится к декорированию обработчиков команд).

Кажется, что загрузка изображения, а затем присвоение нового URL изображения в базе данных является ответственностью одного обработчика команд. Если вы хотите, чтобы уточнял этих двух различных операций, которые нужно абстрагировать, затем введите свои обработчики классами, которые делают это, например IUserimageUploader.

Вообще

Обычно команды считаются незыблемыми, и не должны быть изменены после создания. Это поможет обеспечить выполнение команд, которые должны содержать перед собой всю необходимую информацию для завершения операции.

+0

Я пытался следовать принципу единой ответственности, но один обработчик команд сделало бы это намного проще –

+0

Да, я могу видеть аргумент SRP. Другим подходом будет двухступенчатая функция UploadUserImageCommand -> UploadUserImageCommandHandler -> выполняет новый AssignUpdatedUserImageUrlCommand (userId, url) -> AssignUpdatedUserImageUrlCommandHandler. – Pete

+0

Команды непреложного аргумента решили мою заботу, спасибо. –