Architecture-center: В чем разница между ProductsCommandHandler (CQRS) и ProductRepository (традиционная арка)?

Созданный на 15 нояб. 2019  ·  5Комментарии  ·  Источник: MicrosoftDocs/architecture-center

Привет,
Спасибо, что написали эту статью о CQRS. Я пытаюсь понять, как выглядит реализация, и просматривал примеры. Образец кода репозитория, представленный в примере, очень похож на то, как обычно можно писать репо, за исключением соглашения об именах. Например, я думаю, что репозиторий, приведенный ниже, такой же, как ProductsCommandHandler и, вероятно, одно отличие будет заключаться в том, что здесь нет GetProduct который мы обычно добавляем. Не могли бы вы объяснить, чего я здесь не понимаю?

public class ProductRepository {
  void AddNewProduct(Product newProduct) {
    ...
  }
  void RateProduct(int productId, int userId, int rating) {
    var product = repository.Find(productId);
    if (product != null)
    {
      product.RateProduct(userId, rating);
      repository.Save(product);
    }
  }
}

Детали документа

Не редактируйте этот раздел.

Pri1 architecture-centesvc assigned-to-author cloud-fundamentalsubsvc product-question triaged

Самый полезный комментарий

@martinmthomas На самом деле обработчик команд не является репозиторием. Он использует репозиторий.

Обработчик команд предназначен для «обработки фактических команд, если они подходят». В этом примере класс ProductsCommandHandler получает в своем конструкторе IRepository<Product> .

Допустим, пользователь собирается поставить товар 5555 на 4 звезды.

  • Пользователь видит интерфейс. Скажем, веб-страница (это может быть локальная программа .exe или что-то еще, представьте себе веб-сайт).
  • Пользователь нажимает кнопку. Скажем, это запускает AJAX POST, идущий по маршруту / скорости с данными {"product":"5555","stars":4}
  • Маршрут POST имеет контроллер. Этот контроллер связан с операцией «записи», поскольку она исходит из вызова POST.
  • Контроллер здесь в классическом подходе просто загрузит классический репозиторий продукта и загрузит продукт 5555. Затем скажите продукту «теперь вам присвоено 4 звезды» и сохраните.
  • В этом подходе контроллер создает команду RateProduct и заполняет продукт 5555, звездочки 4. В этом примере он также заполняет, кто оценивает.
  • В этот момент контроллер «отправляет команду» модели записи. Здесь у вас есть 2 варианта: вызвать обработчик команд или поставить его в очередь.
  • Скажем, у вас нет очереди. Затем вы получаете CommandHandler в своем контроллере (вероятно, путем внедрения зависимостей) и просто помещаете туда команду: h.Handle (c); где c - команда RateProduct.
  • Скажем, у вас очередь. Затем вы получаете очередь в своем контроллере (возможно, путем внедрения зависимостей) и просто вставляете в нее команду: q.Queue (c);
  • В этом последнем случае слушатель очереди возьмет команду из очереди и вместо этого вызовет обработчик команд.
  • В качестве третьего варианта (рекомендуется) ваш контроллер не выбирает, будет ли команда обрабатываться синхронно (получить обработчик) или асинхронно (получить очередь), но должен использовать «общий подход», который заключается в использовании командной шины, в которой команды расположены. Скажем, командная шина - b, тогда вы бы сделали b.Send (c); где c - команда RateProduct.
  • В этом третьем подходе вы помещаете обработчик команд в конец шины и можете настроить промежуточное ПО так, чтобы оно «снимало команду с шины и помещало ее в очередь».

Итак, 3 варианта: а) получить обработчик, б) получить очередь, в) получить шину и позволить ей решить отправить команду обработчику или очереди.

Какой бы подход вы ни выбрали ... главное отличие «классического репозитория» в том, что вы НИКОГДА не вызываете сам репозиторий в контроллере. Контроллер не «знает, как управлять сущностями» (классический репозиторий), но знает, «как управлять НАМЕРЕНИЯМИ ДЕЙСТВИЙ с сущностями» (обработчик команд).

Затем это обработчик команд, который, как следует из его названия, "обрабатывает команду", которая пришла откуда-то (веб-контроллер html, контроллер API, командная строка, что угодно), которая была отправлена ​​как намерение, и это CommandHandler (который принадлежит стороне записи), которая решает, что делать с этой командой.

Например, CommandHandler может использовать репо для получения продукта, установки состояния и сохранения (как в примере), но он также может записывать в журнал событий, запускать элементы для обновления сторон чтения, запускать внешние соединители или что-то еще.

Пользовательский интерфейс, основанный на задачах, позволяющий оценить продукт и его контроллер как AGNOSTIC относительно того, «где» размещена или хранится система звездочек / рейтингов. Представьте, что вы разрабатываете систему с нуля, а класс Product уже содержит метод RateProduct (), как в примере. Хорошо.

Но ... что, если у вас есть устаревшая система со "старым" подходом к Продукту. В «модели» (например, Business Mind) нет «рейтинга» продукта. Вместо этого маркетолог хочет «добавить» рейтинг к существующему продукту, но вся компания соглашается, что это «внешняя вещь». Вы бы использовали репозиторий продукта? Или, может быть, другое вспомогательное хранилище, чтобы вы не «трогали» уже работающие и полностью протестированные продукты и репозиторий продуктов?

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

Затем обработчик решит, можно ли использовать ProductRepository или какой-либо другой подход для хранения «рейтинга продукта».

Итак, чтобы ответить:

CommandHandler => не имеет ничего общего с объектами. Он обрабатывает «намерения пользователя» (которые в конечном итоге могут заключаться в изменении сущностей; поэтому наиболее вероятно, что обработчик команд использует репозиторий).
Репозиторий => фактическое хранилище для определенного объекта.

Надеюсь на помощь.
Хави.

Все 5 Комментарий

@martinmthomas Спасибо за вопрос! Мы рассмотрим и предоставим обновления по мере необходимости.

@MikeWasson какие-нибудь мысли здесь?

AB № 160217 - Благодарим за сообщение - эта проблема находится на рассмотрении

@martinmthomas На самом деле обработчик команд не является репозиторием. Он использует репозиторий.

Обработчик команд предназначен для «обработки фактических команд, если они подходят». В этом примере класс ProductsCommandHandler получает в своем конструкторе IRepository<Product> .

Допустим, пользователь собирается поставить товар 5555 на 4 звезды.

  • Пользователь видит интерфейс. Скажем, веб-страница (это может быть локальная программа .exe или что-то еще, представьте себе веб-сайт).
  • Пользователь нажимает кнопку. Скажем, это запускает AJAX POST, идущий по маршруту / скорости с данными {"product":"5555","stars":4}
  • Маршрут POST имеет контроллер. Этот контроллер связан с операцией «записи», поскольку она исходит из вызова POST.
  • Контроллер здесь в классическом подходе просто загрузит классический репозиторий продукта и загрузит продукт 5555. Затем скажите продукту «теперь вам присвоено 4 звезды» и сохраните.
  • В этом подходе контроллер создает команду RateProduct и заполняет продукт 5555, звездочки 4. В этом примере он также заполняет, кто оценивает.
  • В этот момент контроллер «отправляет команду» модели записи. Здесь у вас есть 2 варианта: вызвать обработчик команд или поставить его в очередь.
  • Скажем, у вас нет очереди. Затем вы получаете CommandHandler в своем контроллере (вероятно, путем внедрения зависимостей) и просто помещаете туда команду: h.Handle (c); где c - команда RateProduct.
  • Скажем, у вас очередь. Затем вы получаете очередь в своем контроллере (возможно, путем внедрения зависимостей) и просто вставляете в нее команду: q.Queue (c);
  • В этом последнем случае слушатель очереди возьмет команду из очереди и вместо этого вызовет обработчик команд.
  • В качестве третьего варианта (рекомендуется) ваш контроллер не выбирает, будет ли команда обрабатываться синхронно (получить обработчик) или асинхронно (получить очередь), но должен использовать «общий подход», который заключается в использовании командной шины, в которой команды расположены. Скажем, командная шина - b, тогда вы бы сделали b.Send (c); где c - команда RateProduct.
  • В этом третьем подходе вы помещаете обработчик команд в конец шины и можете настроить промежуточное ПО так, чтобы оно «снимало команду с шины и помещало ее в очередь».

Итак, 3 варианта: а) получить обработчик, б) получить очередь, в) получить шину и позволить ей решить отправить команду обработчику или очереди.

Какой бы подход вы ни выбрали ... главное отличие «классического репозитория» в том, что вы НИКОГДА не вызываете сам репозиторий в контроллере. Контроллер не «знает, как управлять сущностями» (классический репозиторий), но знает, «как управлять НАМЕРЕНИЯМИ ДЕЙСТВИЙ с сущностями» (обработчик команд).

Затем это обработчик команд, который, как следует из его названия, "обрабатывает команду", которая пришла откуда-то (веб-контроллер html, контроллер API, командная строка, что угодно), которая была отправлена ​​как намерение, и это CommandHandler (который принадлежит стороне записи), которая решает, что делать с этой командой.

Например, CommandHandler может использовать репо для получения продукта, установки состояния и сохранения (как в примере), но он также может записывать в журнал событий, запускать элементы для обновления сторон чтения, запускать внешние соединители или что-то еще.

Пользовательский интерфейс, основанный на задачах, позволяющий оценить продукт и его контроллер как AGNOSTIC относительно того, «где» размещена или хранится система звездочек / рейтингов. Представьте, что вы разрабатываете систему с нуля, а класс Product уже содержит метод RateProduct (), как в примере. Хорошо.

Но ... что, если у вас есть устаревшая система со "старым" подходом к Продукту. В «модели» (например, Business Mind) нет «рейтинга» продукта. Вместо этого маркетолог хочет «добавить» рейтинг к существующему продукту, но вся компания соглашается, что это «внешняя вещь». Вы бы использовали репозиторий продукта? Или, может быть, другое вспомогательное хранилище, чтобы вы не «трогали» уже работающие и полностью протестированные продукты и репозиторий продуктов?

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

Затем обработчик решит, можно ли использовать ProductRepository или какой-либо другой подход для хранения «рейтинга продукта».

Итак, чтобы ответить:

CommandHandler => не имеет ничего общего с объектами. Он обрабатывает «намерения пользователя» (которые в конечном итоге могут заключаться в изменении сущностей; поэтому наиболее вероятно, что обработчик команд использует репозиторий).
Репозиторий => фактическое хранилище для определенного объекта.

Надеюсь на помощь.
Хави.

Как упоминалось в @xmontero , ProductsCommandHandler и ProductRepository имеют только отношение «имеет». ProductsCommandHandler действует как интерфейс между ProductApi / Controller и ProductRepository. Эта реализация помогает хранить команды отдельно от запросов. В традиционном подходе мы напрямую используем репозиторий из контроллера, сохраняя выполнение команд и запросов вместе, поэтому нет необходимости в каком-либо другом интерфейсе между ними.

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

Была ли эта страница полезной?
0 / 5 - 0 рейтинги