Реализация стратегии извлечения Udi - Как мне искать?

Фон

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

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

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

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

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

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

Вопрос

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

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

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

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

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

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

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

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

Я закончил тем, что использовал IStrategyForFetching<TEntity, TSpecification>, TEntity - это то, что я хочу получить, TSpecification - это то, как я хочу получить это.

2 ответа

Вы сталкивались с CQRS? Udi - большой сторонник этого, и его цель состоит в том, чтобы решить эту точную проблему.

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

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

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

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

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

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

  1. Не пытайтесь спроектировать сокращение DTO от ваших доменных агрегатов. Вместо этого просто создайте отдельные службы запросов, которые предоставят вам индивидуальный запрос для ваших нужд.
  2. Читайте о CQRS (если вы еще этого не сделали).

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

Другие вопросы по тегам