2015-01-05 3 views
2

У меня вопрос о дублировании кода в командной части принципа CQRS.Кодирование кода CQRS в командах

Были следующие статьи из:

https://www.cuttingedge.it/blogs/steven/pivot/entry.php?id=91 https://www.cuttingedge.it/blogs/steven/pivot/entry.php?id=92

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

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

public class ResetPasswordCommandHandler : CommandHandler<ResetPasswordCommand> 
{ 
    public override void Execute(ResetPasswordCommand command) 
    { 
     **// duplication here** 
     var user = from c in db.Users 
      where c.EmailAddress = command.EmailAddress 
      select c; 

     user.EmailAddress = command.EmailAddress; 
     ... 
     db.Save(); 
    } 
} 

public class UpdateLastLoginCommandHandler : CommandHandler<UpdateLastLoginCommand> 
{ 
    public override void Execute(UpdateLastLoginCommand command) 
    { 
     **// duplication here** 
     var user = from c in db.Users 
      where c.EmailAddress = command.EmailAddress 
      select c; 

     user.LastLogin = DateTime.Now; 
     ... 
     db.Save(); 
    } 
} 

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

Я мог бы создать UserRepository, который, например, должен иметь метод GetUserByEmailAddress и вставить этот IUserRepository в конструктор моих CommandHandlers. Однако не создаст ли это «репозиторий богов» с помощью Save, GetById, GetByUsername и т. Д.?

И если я создаю репозиторий, зачем создавать отдельные объекты запроса?

Как можно сохранить этот код СУХОЙ?

ответ

3

Почему не реорганизовать Command Handler в один, который реализует несколько интерфейсов:

public class UserCommandHandler : CommandHandlerBase, 
            IHandle<ResetPasswordCommand>, 
            IHandle<UpdateLastLoginCommand> 
{ 
    public void Execute(ResetPasswordCommand command) 
    { 
     var user = GetUserByEmail(command.EmailAddress); 

     user.EmailAddress = command.EmailAddress; 
     ... 
     db.Save(); 
    } 

    public void Execute(UpdateLastLoginCommand command) 
    { 
     var user = GetUserByEmail(command.EmailAddress); 

     user.LastLogin = DateTime.Now; 
     ... 
     db.Save(); 
    } 

    private User GetUserByEmail(string email) { 
      return (from c in db.Users 
        where c.EmailAddress = command.EmailAddress 
        select c).FirstOrDefault(); 
    } 
} 

Таким образом, вы можете реорганизовывать частные вспомогательные методы внутри обработчика команд, то команда Обработчик может обрабатывать подобные команды, и вы понизив дублирование кода. Вам также не понадобится репозиторий «Бог».

Лично я предпочел бы иметь частный GetUserByEmail помощника в качестве отдельного класса запросов, я впрыснуть db контекст через конструктор, так что GetUserByEmail очень специфический класс, который получает мне User.

Надеюсь, это поможет.

+0

Не рефакторинг обработчика команд для реализации нескольких интерфейсов просто создаст обработчик команд «Бог», а не раздутый репозиторий? – user4419743

+0

Мне нравится вторая часть. Возможно, создайте метод расширения. Что-то вроде: 'public static IQueryable WithEmailAddress (этот IQueryable источник, строка emailaddress)' – user4419743

1

Это не создаст хранилище богов. Это будет правильный репозиторий, имеющий метод, используемый в случае использования команды. Но это означает, что вы используете правильный шаблон репозитория, что означает отсутствие IQueryable или EF. Помните, что в репозитории домена есть методы запросов, которые нужны только в случаях использования домена, а в репозитории используются только корни доменных агрегатов (целые объекты домена).

Ваш текущий подход заключается в использовании EF (DAO в конце) в ваших приложениях, и это означает, что прикладной уровень и, возможно, доменный уровень также связаны с EF. Ваши сервисы высокого уровня (обработчик команд в конце концов, реализация прецедента) не должны делать запросы, потому что с их точки зрения нет rdbms i.e, они не знают о запросах.

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

1

Я знаю, что это похоже на дублирование кода. Я знаю, что похоже, что вы идете против директоров DRY, но я могу заверить вас, что код общедоступного репозитория сервиса в поведении никогда не является хорошей идеей. Одна из проблем заключается в том, что каждое поведение может потребовать несколько разных реализаций GetUser по электронной почте. Один из них требует полного имени, а другой - нет. Разделяя этот код, вы эффективно связываете оба вызова. Одно из действий теперь может потребовать дополнительной части данных, возвращаемых, но теперь, когда вы подсоединили друг к другу, когда другая реализация вызывает эту «общую службу», у нее есть накладные расходы на все дополнительные данные, которые она не требует.

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

Смежные вопросы