Насколько я понимаю, основная идея CQRS - это две разные модели данных для обработки команд и запросов. Они называются «модель записи» и «модель чтения».
Давайте рассмотрим пример клонирования приложения Twitter. Вот команды:
- Пользователи могут зарегистрироваться самостоятельно.
CreateUserCommand(string username)
излучаетUserCreatedEvent
- Пользователи могут подписаться на других пользователей.
FollowUserCommand(int userAId, int userBId)
излучаетUserFollowedEvent
- Пользователи могут создавать сообщения.
CreatePostCommand(int userId, string text)
излучаетPostCreatedEvent
Хотя я использую термин «событие» выше, я не имею в виду события «источник событий». Я просто имею в виду сигналы, которые запускают обновления модели чтения. У меня нет магазина событий, и я пока хочу сконцентрироваться на самом CQRS.
И вот вопросы:
- Пользователь должен видеть список своих сообщений.
GetPostsQuery(int userId)
- Пользователь должен видеть список своих подписчиков.
GetFollowersQuery(int userId)
- Пользователь должен видеть список пользователей, за которым он следует.
GetFollowedUsersQuery(int userId)
- Пользователь должен увидеть «ленту друзей» - журнал всех действий своих друзей («ваш друг Джон только что создал новую запись»).
GetFriedFeedRecordsQuery(int userId)
Чтобы справиться, CreateUserCommand
мне нужно знать, существует ли такой пользователь. Итак, на данный момент я знаю, что моя модель записи должна иметь список всех пользователей.
Чтобы справиться, FollowUserCommand
мне нужно знать, следует ли userA уже за userB или нет. На данный момент я хочу, чтобы моя модель записи имела список всех подключений пользователя-пользователя.
И, наконец, для обработки CreatePostCommand
я не думаю, что мне нужно что-то еще, потому что у меня нет таких команд, как UpdatePostCommand
. Если бы они были у меня, мне нужно было бы убедиться, что этот пост существует, поэтому мне нужен список всех постов. Но поскольку у меня нет этого требования, мне не нужно отслеживать все сообщения.
Вопрос № 1 : действительно ли правильно использовать термин «модель записи» так, как я его использую? Или «модель записи» всегда означает «хранилище событий» в случае ES? Если да, есть ли какое-либо разделение между данными, которые мне нужны для обработки команд, и данными, которые мне нужны для обработки запросов?
Чтобы справиться GetPostsQuery
, мне нужен список всех сообщений. Это означает, что моя модель чтения должна иметь список всех сообщений. Я собираюсь поддержать эту модель, слушая PostCreatedEvent
.
Чтобы справиться с обоими GetFollowersQuery
и GetFollowedUsersQuery
мне, мне нужен список всех соединений между пользователями. Для поддержания этой модели я буду слушать UserFollowedEvent
. Вот вопрос № 2 : это нормально, если я использую здесь список соединений модели записи? Или мне лучше создать отдельную модель чтения, потому что в будущем мне может понадобиться больше деталей, чем в модели записи?
Наконец, для обработки GetFriendFeedRecordsQuery
мне понадобится:
- Слушать
UserFollowedEvent
- Слушать
PostCreatedEvent
- Знайте, какие пользователи следуют за какими другими пользователями
Если пользователь A следует за пользователем B, а пользователь B начинает следовать за пользователем C, должны появиться следующие записи:
- Для пользователя A: «Ваш друг пользователь B только что подписался на пользователя C»
- Для пользователя B: «Вы только что подписались на пользователя C»
- Для пользователя C: «Пользователь B теперь следует за вами»
Вот вопрос № 3 : какую модель я должен использовать, чтобы получить список соединений? Должен ли я использовать модель записи? Должен ли я использовать модель чтения - GetFollowersQuery
/ GetFollowedUsersQuery
? Или я должен заставить GetFriendFeedRecordsQuery
саму модель обрабатывать UserFollowedEvent
и поддерживать свой собственный список всех соединений?
источник
Ответы:
Грег Янг (2010)
Если вы думаете с точки зрения разделения командных запросов Бертрана Мейера , вы можете представить модель с двумя различными интерфейсами: один, поддерживающий команды, и другой, поддерживающий запросы.
Идея Грега Янга заключалась в том, что вы можете разделить это на два отдельных объекта
Разделив объекты, теперь у вас есть возможность отделить структуры данных, которые хранят состояние объекта в памяти, так что вы можете оптимизировать для каждого случая; или сохранить / сохранить состояние чтения отдельно от состояния записи.
Под термином WriteModel обычно понимается изменчивое представление модели (т. Е. Объект, а не хранилище данных).
TL; DR: я думаю, что вы используете хорошо.
Это "хорошо" - иш. Концептуально, нет ничего плохого в том, что модель чтения и модель записи имеют одинаковые структуры.
На практике, поскольку записи в модель обычно не являются атомарными, существует потенциальная проблема, когда один поток пытается изменить состояние модели, в то время как второй поток пытается ее прочитать.
Использование модели записи - неправильный ответ.
Написание нескольких моделей чтения, каждая из которых настроена на конкретный вариант использования, вполне разумно. Мы говорим «модель чтения», но подразумевается, что может быть много моделей чтения, каждая из которых оптимизирована для конкретного варианта использования и не реализует случаи, когда это не имеет смысла.
Например, вы можете решить использовать хранилище значений ключей для поддержки некоторых запросов, а также базу данных графов для других запросов или реляционную базу данных, в которой эта модель запросов имеет смысл. Лошади на курсы.
В ваших конкретных обстоятельствах, когда вы все еще изучаете шаблон, я бы предложил сохранить ваш проект "простым" - иметь одну модель чтения, которая не разделяет структуру данных модели записи.
источник
Я бы посоветовал вам учитывать, что у вас есть одна концептуальная модель данных.
Тогда модель записи является материализацией этой модели данных, которая оптимизирована для обновлений транзакций. Иногда это означает нормализованную реляционную базу данных.
Модель чтения - это материализация той же модели данных, оптимизированная для выполнения запросов, в которых нуждаются ваши приложения. Это может быть реляционная база данных, хотя она намеренно денормализована для обработки запросов с меньшим количеством соединений.
(# 1) Для CQRS модель записи не обязательно должна быть хранилищем событий.
(# 2) Я не ожидал бы, что модель чтения будет хранить что-либо, что не входит в модель записи, например, потому что в CQRS никто не обновляет модель чтения, кроме механизма пересылки, который поддерживает синхронизацию модели чтения с изменениями в напиши модель.
(# 3) В CQRS запросы должны идти против модели чтения. Вы можете сделать по-другому: все в порядке, просто не следуя CQRS.
Таким образом, существует только одна концептуальная модель данных. CQRS отделяет командную сеть и возможности от сети запросов и возможностей. Учитывая это разделение, модель записи и модель чтения могут быть размещены с использованием совершенно разных технологий в качестве оптимизации производительности.
источник