Внедрение стратегии поиска Udi: как искать?

Задний план

Уди Дахан предлагает стратегию получения в качестве полезного шаблона для использования. для доступа к данным. Я согласен.

Идея состоит в том, чтобы сделать роли явными. Например, у меня есть Совокупный корень — Клиент. Я хочу, чтобы клиент был в нескольких частях моего приложения: список клиентов для выбора, просмотр сведений о клиенте и кнопка для деактивации клиента.

Кажется, Уди предложил бы интерфейс для каждой из этих ролей. Итак, у меня есть ICustomerInList с очень простой информацией, ICustomerDetail, которая включает последние 10 приобретенных продуктов, и IDeactivateCustomer, в которой есть метод деактивации клиента. Каждый интерфейс предоставляет ровно столько корневой совокупности клиентов, сколько необходимо для выполнения работы в каждой ситуации. My Customer Aggregate Root реализует все эти интерфейсы.

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

Общий метод реализации этой части — запросить Service Locator или какой-либо другой стиль внедрения зависимостей. Этот код возьмет нужный вам интерфейс, например ICustomerInList, и найдет стратегию загрузки для его загрузки (IStrategyForFetching<ICustomerInList>). Эта стратегия реализуется классом, который знает, что нужно загружать в Customer только биты информации, необходимые для интерфейса ICustomerInList.

Все идет нормально.

Вопрос

То, что вы передаете в Service Locator, или файл IStrategyForFetching<ICustomerInList>. Все примеры, которые я вижу, выбирают только один объект по известному идентификатору. Этот случай прост, вызывающий код передает этот идентификатор и возвращает конкретный интерфейс.

А если я хочу поискать? Или я хочу страницу 2 списка клиентов? Теперь я хочу передать больше терминов, необходимых для стратегии выборки.

Возможные решения

В некоторых примерах, которые я видел, используется предикат — выражение, которое возвращает true или false, если конкретный совокупный корень должен быть частью результирующего набора. Это хорошо работает для условий, но как насчет того, чтобы вернуть первых n клиентов и не более? Или получение страницы 2 результатов поиска? Или как сортируются результаты?

Моя первая реакция — начать добавлять общие параметры к моему IStrategyForFetching<ICustomerInList>. Теперь он становится IStrategyForFetching<TAggregateRoot, TStrategyForSelecting, TStrategyForOrdering>. Это быстро становится сложным и уродливым. Это еще больше усложняется разными репозиториями. Некоторые репозитории предоставляют данные только при использовании определенной стратегии выбора, некоторые только определенные типы упорядочения. Я хотел бы иметь гибкость для реализации общих репозиториев, которые могут принимать функции сортировки вместе со специализированными репозиториями, которые возвращают только совокупные корни, отсортированные определенным образом.

Похоже, я должен применить тот же шаблон, который использовался в начале — как сделать роли явными? Должен ли я реализовать стратегию получения X (Agggregate Root) с использованием полезной нагрузки Y (параметры поиска/упорядочивания)?

Изменить (2012-03-05)

Все это по-прежнему действует, если я не возвращаю совокупный корень каждый раз. Если каждый интерфейс реализуется другим DTO, я все равно могу использовать IStrategyForFetching. Вот почему этот паттерн является мощным — то, что делает выборка и что возвращается, не должно каким-либо образом сопоставляться с корнем агрегата.

В итоге я использовал IStrategyForFetching<TEntity, TSpecification>. TEntity — это то, что я хочу получить, TSpecification — это то, как я хочу это получить.


person Thomas Coats    schedule 29.02.2012    source источник


Ответы (2)


Сталкивались ли вы с CQRS? Уди горячий сторонник этого, и его цель – решить именно этот вопрос.

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

Краткая последовательность того, как это работает:

  • На экране отображается список клиентов, которые сделали заказы за последнюю неделю.
  • Пользовательский интерфейс вызывает CustomerQueryService, передавая дату в качестве критерия.
  • CustomerQueryService выполняет запрос, который возвращает только поля, необходимые для этого экрана, включая совокупный идентификатор каждого клиента.
  • Пользователь выбирает клиента в списке и выбирает действие/команду «Сделать важным клиентом».
  • Пользовательский интерфейс отправляет MakeImportantCommand в службу команд (или службу приложений в терминах DDD), содержащую идентификатор клиента.
  • Командный сервис извлекает агрегат Customer из репозитория по идентификатору, переданному в команде, вызывает необходимые методы и обновляет базу данных.

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

Я знаю, что ваш вопрос касался «стратегии выборки», но я заметил, что он написал эту статью в 2007 году и вполне вероятно, что он считает CQRS ее преемником.

Подводя итог моего ответа:

  1. Не пытайтесь сократить DTO из агрегатов вашего домена. Вместо этого просто создайте отдельные службы запросов, которые дадут вам индивидуальный запрос для ваших нужд.
  2. Почитайте CQRS (если вы еще этого не сделали).
person David Masters    schedule 02.03.2012
comment
Спасибо за эту перспективу! Я использую эту технику вместе с CQRS, о ​​которой много читал. IStrategyForFetching<ICustomerInList> не обязательно должен возвращать совокупность, он может возвращать прогноз только для клиентов в списках. Я предлагаю вместо того, чтобы разговаривать с CustomerQueryService напрямую, мы можем запросить роль ICustomerInList, и часть системы знает, как получить ее для нас. Это похоже на шаблон интерфейса маркера IValidate<Customer>. - person Thomas Coats; 02.03.2012

Чтобы добавить к ответу David Masters, я думаю, что все интерфейсы стратегии выборки добавляют ненужную сложность. Наличие у клиента AR реализации различных интерфейсов, которые смоделированы по образцу пользовательского интерфейса, является ненужным ограничением для класса AR, и вы потратите слишком много усилий, пытаясь обеспечить его соблюдение. Кроме того, это хрупкий раствор. Что если для представления требуются данные, которые хотя и связаны с клиентом, но не относятся к классу клиентов? Принуждает ли затем класс клиента и соответствующие отображения ORM содержать эти данные? Почему бы просто не создать отдельный набор классов для запросов и покончить с этим? Это позволяет вам иметь дело со стратегиями выборки там, где им и место — в репозитории. Кроме того, какую ценность на самом деле добавляет абстракция интерфейса стратегии выборки? Это может быть подходящей моделью того, что происходит в приложении, но не помогает в его реализации.

person eulerfx    schedule 03.03.2012
comment
Опять же, Aggregate Root не обязательно должен реализовывать каждый интерфейс — можно использовать DTO. Эти интерфейсы можно рассматривать как модели представлений. Уди предполагает, что уровень службы изменяется с другой скоростью, чем уровень домена. В результате хорошо иметь слой косвенности между каждым слоем. Я могу запросить у «системы» ICustomerInList, и через CQRS я читаю DTO прямо из базы данных. Мой вопрос касается «системы». IStrategyForFetching против IFetchCustomersInLists. - person Thomas Coats; 05.03.2012