Как репозитории подходят для CQRS?
согласно Фаулеру (здесь), репозиторий " посредничает между слоями отображения домена и данных, действуя как коллекция объектов домена в памяти."Так, например, в моем приложении курьерской службы при отправке нового запуска моя служба приложений создает новый объект Run aggregate root, заполняет его значениями из запроса, затем добавляет его в RunRepository перед вызовом единицы работы для сохранения изменений в базе данных. Когда пользователь хочет просмотреть список текущих запусков, я запрашиваю тот же репозиторий и возвращаю денормализованный DTO, представляющий информацию.
однако при просмотре CQRS запрос не попадет в тот же репозиторий. Вместо этого она бы, наверное, перейти непосредственно к хранилищу данных и всегда быть денормализованы. И моя командная сторона будет развиваться до NewRunCommand и обработчика, который создаст и заполнит объект домена NewRun, а затем сохранит информацию в хранилище данных.
Итак, первый вопрос в том, где репозитории вписываются в модель CQRS, если мы не поддерживаем коллекцию в памяти (кэш, если хотите) объектов домена?
рассмотрим случай, когда информация, отправленная в мою службу приложений, содержит только ряд значений ID, которые служба должна разрешить для построения объекта домена. Например, запрос содержит идентификатор курьера, назначенного для выполнения. Служба должна искать фактический объект Courier на основе значение ID и назначить объект NewRun с помощью метода AssignCourier (который проверяет курьера и выполняет другую бизнес-логику).
другой вопрос, учитывая разделение запросов и потенциальное отсутствие репозиториев, как служба приложений выполняет поиск для поиска объекта домена курьера?
обновление
основываясь на некоторых дополнительных чтениях и мыслях после комментария Денниса, я перефразируйте мои вопросы.
Мне кажется, что CQRS поощряет репозитории, которые являются просто фасадами над механизмами доступа к данным и хранения данных. Они дают" внешний вид " коллекции (как описывает Фаулер), но не управляют сущностями в памяти (как указал Деннис). Это означает, что каждая операция в репозитории является сквозной, да?
как единица работы вписывается в этот подход? Обычно процедура используется для фиксации изменений, внесенных в хранилище (правильно?) но если репозиторий не поддерживает сущности в памяти, то какую роль имеет UoW?
что касается операции "запись", будет ли обработчик команд иметь ссылку на тот же репозиторий, другой репозиторий или, возможно, UoW вместо репозитория?
2 ответов
Я читал о системах CQRS, которые поддерживают простое хранилище значений ключей на стороне команды для представления состояния приложения, и другие, которые просто коррелируют сообщения (используя какую-то сагу) и используют хранилище запросов для представления состояния приложений. В любом случае, без сомнения, с этими подходами будет связана технология персистентности, но шаблон репозитория в этих случаях будет ненужной абстракцией поверх нее.
мой опыт работы с CQRS только когда-либо был с источником событий, хотя, где мы воспроизводили прошлые события, чтобы перестроить агрегаты, которые инкапсулируют и применяют бизнес-логику и инварианты. В этом случае шаблон репозитория является знакомой абстракцией, которая может обеспечить более простой способ извлечения любой из этих агрегатов.
Что касается стороны запроса, я бы рекомендовал как можно ближе подобраться к хранилищу данных, под этим я имею в виду избегать любых репозиториев, служб или фасадов и т. д. между ИП (независимо от это может быть) и ваше хранилище данных.
Это может помочь увидеть пример этих подходов в использовании. Возможно, взгляните на следующее проекты:
- https://github.com/gregoryyoung/m-r/tree/master/SimpleCQRS
- https://github.com/MarkNijhof/Fohjin
- https://github.com/elliotritchie/NES
- https://github.com/ToJans/Scritchy
- http://ncqrs.org
в случае NES репозиторий просто предоставляет знакомый интерфейс для добавления и чтение агрегатов непосредственно в единицу работы и из нее.
еще несколько ссылок, которые могут помочь:
Я не уверен, насколько это ортодоксально , но в текущем проекте у меня есть репозиторий для моего корня aggregate entity. Этот репозиторий имеет только два метода: Get и ApplyEvents.
все события реализуют общий интерфейс для своего типа-для ордеров есть OrderEvents и т. д. Я лично реализовывать бизнес-логику каждого события в полиморфный метод, так что добавление новых типов событий становится очень легко.
для Get репозиторий переходит в хранилище событий и возвращает все события в области для типа (например, заказы на размещение в одном хранилище). Затем он воспроизводит события, чтобы получить текущее состояние объекта для всех событий, которые он дал. Он также может работать из моментального снимка, поэтому вы не воссоздаете каждое событие при каждой загрузке. Вы также можете иметь общий репозиторий событий, чтобы даже абстрагироваться от того, как вы храните события, и извлекать их на основе спецификаций.
ApplyEvents принимает список событий, а затем изменяет состояние сущности, основанное на них, и возвращает его. Обратите внимание, что вы предоставляете репозиторию возможность воссоздать сущность, а не просто изменить ее! Это хорошо работает с функциональным типом программирования, но означает, что лучше всего избегать равенства объектов (obj1 == obj2) в C# или Java. Я бы сказал, что только ValueObjects, а не сущности, должны иметь равенство в любом случае.
вот как это работает на практике (C#)- У меня есть заказы, и я хочу добавить элемент. currentOrder.Items is возврат пустого списка. Тогда я делаю
Assert.IsFalse(newEvent.Items.Any())
IOrderEvent newEvent = eventFactory.CreateOrderItemEvent(myItemID);
currentOrder = orderRepository.ApplyEvents(currentOrder, newEvent);
Assert.IsTrue(newEvent.Items.Any())
теперь я должен увидеть currentOrder.Элементы имеют одну запись.
недостатками здесь являются то, что вся моя обработка выполняется через события, а не моя бизнес-логика в сущности. Однако в моем случае, когда почти все мои объекты должны быть сериализуемыми (в основном POCOs) и работать на нескольких системах, это на самом деле хорошо работает.