Реализация стратегии извлечения 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 своим преемником.
Подводя итог моему ответу:
- Не пытайтесь спроектировать сокращение DTO от ваших доменных агрегатов. Вместо этого просто создайте отдельные службы запросов, которые предоставят вам индивидуальный запрос для ваших нужд.
- Читайте о CQRS (если вы еще этого не сделали).
Чтобы добавить ответ David Masters, я думаю, что все интерфейсы стратегии извлечения добавляют ненужную сложность. Наличие AR-клиента, реализующего различные интерфейсы, которые моделируются после пользовательского интерфейса, является ненужным ограничением для класса AR, и вы приложите немало усилий, чтобы реализовать его. Более того, это хрупкое решение. Что если представлению требуются данные, которые хотя и относятся к клиенту, но не принадлежат к классу клиентов? Приводит ли затем к классу клиента и соответствующим сопоставлениям ORM эти данные? Почему бы просто не иметь отдельный набор классов для целей запроса и покончить с этим? Это позволяет вам выбирать стратегии в том месте, где они принадлежат - в хранилище. Кроме того, какое значение действительно добавляет абстракция интерфейса стратегии выборки? Это может быть подходящей моделью того, что происходит в приложении, это не помогает в ее реализации.