Как репозитории соответствуют CQRS?

По словам Фаулера (здесь), репозиторий «выступает посредником между уровнями отображения домена и данных, действуя как домен в памяти. коллекция объектов ". Так, например, в моем приложении Courier Service, когда отправляется новый запуск, моя служба приложения создает новый агрегатный корневой объект Run, заполняет его значениями из запроса, а затем добавляет его в RunRepository перед вызовом Unit of Work для сохранения изменения в базе данных. Когда пользователь хочет просмотреть список текущих запусков, я запрашиваю тот же репозиторий и возвращаю денормализованный DTO, представляющий информацию.

Однако при просмотре CQRS запрос не попадет в тот же репозиторий. Вместо этого он, возможно, будет идти прямо против хранилища данных и всегда денормализован. И моя командная сторона эволюционировала бы до NewRunCommand и Handler, которые будут создавать и заполнять объект домена NewRun, а затем сохранять информацию в хранилище данных.

Итак, первый вопрос: где репозитории вписываются в модель CQRS, если мы не поддерживаем коллекцию в памяти (кеш, если хотите) объектов домена?

Рассмотрим случай, когда информация, отправленная в мою службу приложения, не содержит ничего, кроме серии значений идентификаторов, которые служба должна разрешить, чтобы построить объект домена. Например, запрос содержит ID # курьера, назначенного для выполнения. Служба должна найти фактический объект Courier на основе значения идентификатора и назначить объект NewRun с помощью метода AssignCourier (который проверяет курьера и выполняет другую бизнес-логику).

Другой вопрос: с учетом разделения запросов и возможного отсутствия репозиториев, как служба приложения выполняет поиск, чтобы найти объект домена Courier?

ОБНОВЛЕНИЕ

Основываясь на дополнительном чтении и размышлениях после комментария Денниса, я перефразирую свои вопросы.

Мне кажется, что CQRS поощряет репозитории, которые просто нависают над механизмами доступа к данным и их хранения. Они создают «видимость» коллекции (как описывает Фаулер), но не управляют сущностями в памяти (как указал Деннис). Это означает, что каждая операция в репозитории является сквозной, да?

Каким образом единица работы вписывается в этот подход? Обычно UoW используется для фиксации изменений, внесенных в репозиторий (верно?), Но если репозиторий не поддерживает объекты в памяти, то какую роль играет UoW?

Что касается операции записи, будет ли обработчик команд иметь ссылку на тот же репозиторий, другой репозиторий или, возможно, UoW вместо репозитория?


person SonOfPirate    schedule 07.04.2012    source источник
comment
Определение Фаулера немного отличается от шаблона репозитория Эрика Эвана, который он описывает в книге DDD. Я думаю, что CQRS склоняется к определению Эвана.   -  person Dennis Traub    schedule 08.04.2012
comment
Пожалуйста, объясни. Единственная разница, которую я вижу, - это аспект в памяти, но использование будет таким же, верно?   -  person SonOfPirate    schedule 08.04.2012


Ответы (2)


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

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

Что касается стороны запроса, я бы рекомендовал подойти как можно ближе к хранилищу данных, под этим я подразумеваю избегать любых репозиториев, служб, фасадов и т. Д. Между вашим пользовательским интерфейсом (каким бы он ни был) и вашим хранилищем данных.

Возможно, вам будет полезно увидеть пример использования этих подходов. Может быть, взгляните на следующие проекты:

В случае NES репозиторий просто предоставляет знакомый интерфейс для добавления и чтения агрегатов непосредственно в единицу работы и из нее.

Еще несколько ссылок, которые могут помочь:

person Elliot Ritchie    schedule 18.04.2012
comment
Я согласен с тем, что в большинстве примеров CQRS сочетается с Event Sourcing. Тем не менее, я придерживаюсь того же мышления, что и Уди Дахан, который указывает, что эта взаимосвязь является выбором реализации, а не частью шаблона CQRS. - person SonOfPirate; 18.04.2012
comment
Event Sourcing добавляет идею о том, что мы сохраняем изменения как транзакции, а не сохраняем состояние наших сущностей. Я вижу ценность этого, но, учитывая специальные знания и мыслительный процесс, которые для этого требуются, это трудный подход. В моем случае я имею дело с множеством институциональных разработчиков и устаревшими приложениями, включая устаревшую базу данных, которую я должен продолжать использовать. Таким образом, моя реализация CQRS не включает Event Sourcing. - person SonOfPirate; 18.04.2012
comment
Вы можете столкнуться с трудностями при синхронизации вашей модели событий и модели, представляющей состояние ваших приложений. - person Elliot Ritchie; 18.04.2012
comment
Не уверен, что вы имеете в виду. Как я уже сказал, мы не используем Event Sourcing. - person SonOfPirate; 19.04.2012
comment
Вы вообще публикуете события? - person Elliot Ritchie; 19.04.2012
comment
Не в том смысле, в котором это делает Event Sourcing. Если мы инициируем событие, это строго как операция обмена сообщениями. То есть, чтобы другая часть приложения знала о значительном событии. Мы не используем его как часть нашего механизма сохранения. - person SonOfPirate; 19.04.2012
comment
Чтобы попытаться ответить на вопрос еще раз - я бы использовал неуниверсальный репозиторий на моей командной стороне, чтобы предоставить четкий контракт на получение объектов моей модели предметной области (FindCustomerById, FindCustomerByName и т. Д.). Репозиторий может делегировать единицу работы, которая будет управлять коллекцией любых извлеченных объектов в памяти. Что касается запроса, я читал напрямую из хранилища данных - без репозиториев. - person Elliot Ritchie; 19.04.2012

Я не уверен, насколько это ортодоксально, но в текущем проекте у меня есть репозиторий для моего совокупного корня сущности. В этом репозитории есть только два метода: Get и ApplyEvents.

Все события реализуют общий интерфейс для своего типа - для заказов есть OrderEvents и т. Д. Я лично поместил бизнес-логику каждого события в полиморфный метод, так что добавление новых типов событий становится очень простым.

Для Get репозиторий переходит в хранилище событий и получает все события в области видимости для данного типа (например, заказы на одно место в магазине). Затем он выполняет воспроизведение событий, чтобы прийти к текущему состоянию объекта для всех заданных событий. Он также может работать с моментальным снимком, поэтому вы не воссоздаете каждое событие при каждой загрузке. У вас также может быть общий репозиторий событий, чтобы даже абстрагироваться от того, как вы храните события, и извлекать их на основе спецификаций.

ApplyEvents принимает список событий, а затем на их основе изменяет состояние объекта и возвращает его. Обратите внимание, что вы даете репозиторию возможность воссоздать объект, а не просто изменить его! Это хорошо работает с функциональным типом программирования, но означает, что лучше избегать равенства объектов (obj1 == obj2) в C # или Java. Я бы сказал, что только ValueObjects, а не Entities, в любом случае должны иметь равенство.

Вот как это работает на практике (C #) - у меня есть заказы, и я хочу добавить элемент. currentOrder.Items возвращает пустой список. Тогда я делаю

Assert.IsFalse(newEvent.Items.Any())
IOrderEvent newEvent = eventFactory.CreateOrderItemEvent(myItemID);
currentOrder = orderRepository.ApplyEvents(currentOrder, newEvent);
Assert.IsTrue(newEvent.Items.Any())

Теперь я должен увидеть currentOrder.Items имеет одну запись.

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

person Mathieson    schedule 13.06.2014