CQRS без DDD і без (або з?) ES - що таке модель запису і що читається модель?


11

Наскільки я розумію, великою ідеєю 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мені потрібно знати, чи користувачA вже переглядає 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та підтримувати свій власний список всіх підключень?


Майте на увазі, що в системах CQRS модель запитів даних і модель даних Command можуть використовувати дані з різних баз даних. І обидві моделі можуть жити незалежно одна від одної (різні програми). Якщо говорити, відповідь "залежить" (як завжди). Це може зацікавити
Laiv

Відповіді:


7

Грег Янг (2010)

CQRS - це просто створення двох об'єктів, де раніше був лише один.

Якщо ви думаєте з точки зору розділення запитів команд Бертрана Мейєра , ви можете вважати, що модель має два чіткі інтерфейси, той, який підтримує команди, і той, що підтримує запити.

interface IChangeTheModel {
    void createUser(string username)
    void followUser(int userAId, int userBId)
    void createPost(int userId, string text)
}

interface IDontChangeTheModel {
    Iterable<Post> getPosts(int userId)
    Iterable<Follower> getFollowers(int userId)
    Iterable<FollowedUser> getFollowedUsers(int userId)
}

class TheModel implements IChangeTheModel, IDontChangeTheModel {
    // ...
}

Грег Янг зрозумів, що ви можете розділити це на два окремих об'єкти

class WriteModel implements IChangeTheModel { ... }
class ReadModel  implements IDontChangeTheModel {...}

Розділивши об’єкти, тепер у вас є можливість відокремити структури даних, що зберігають стан об'єкта в пам'яті, щоб ви могли оптимізувати для кожного випадку; або зберігати / зберігати стан читання окремо від стану запису.

Запитання №1: чи правильно використовувати термін "модель запису", як я його використовую? Або "запис моделі" завжди означає "магазин подій" у випадку ES? Якщо так, чи існує якийсь поділ між даними, які мені потрібні для обробки команд, та даними, які мені потрібні для обробки запитів?

Термін WriteModel зазвичай розуміється як змінне зображення представленої моделі (тобто: об'єкт, а не зберігання).

TL; DR: Я думаю, що ваше використання добре.

Ось питання № 2: чи практично це нормально, якщо тут я використовую список моделей записів?

Це "добре" - іш. Концептуально немає нічого поганого в моделі читання та моделі запису, що розділяє ті самі структури.

На практиці, оскільки записи в модель, як правило, не є атомними, можливо, існує проблема, коли одна нитка намагається змінити стан моделі, а друга нитка намагається її прочитати.

Ось питання №3: яку модель я повинен використовувати для отримання списку підключень? Чи варто використовувати модель запису? Чи варто використовувати модель читання - GetFollowersQuery / GetFollowedUsersQuery? Або я повинен зробити так, щоб модель GetFriendFeedRecordsQuery сама обробляла UserFollowedEvent і підтримувала власний список всіх підключень?

Використання моделі запису - неправильна відповідь.

Написання декількох моделей читання, де кожна налаштована на певний випадок використання, цілком розумно. Ми говоримо "модель читання", але розуміється, що може бути багато моделей для читання, кожна з яких оптимізована для конкретного випадку використання, і не реалізує випадки, коли це не має сенсу.

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

За вашої конкретної обставини, коли ви все ще вивчаєте цю схему, моя пропозиція полягає в тому, щоб зробити ваш дизайн "простим" - мати одну модель для читання, яка не поділяє структуру даних моделі запису.


1

Я б закликав вас врахувати, що у вас є одна концептуальна модель даних.

Тоді модель запису - це матеріалізація тієї моделі даних, яка оптимізована для оновлення транзакцій. Іноді це означає нормалізовану реляційну базу даних.

Модель читання - це матеріалізація тієї самої моделі даних, оптимізованої для виконання запитів, які потрібні ваші додатки. Це все ще може бути реляційною базою даних, хоча цілеспрямовано денормалізована для обробки запитів із меншою кількістю приєднань.


(# 1) Для CQRS модель запису не повинна бути сховищем подій.

(# 2) Я не очікував, що модель читання зберігає щось, що не є в моделі запису, для однієї, тому що в CQRS ніхто не оновлює модель читання, окрім механізму переадресації, який підтримує синхронізовану модель з синхронізацією зі змінами до написати модель.

(# 3) У CQRS запити повинні відповідати моделі читання. Ви можете робити різні дії: це нормально, тільки не слідкуйте за CQRS.


Підсумовуючи, існує лише одна концептуальна модель даних. CQRS відокремлює командну мережу та можливості від мережі запитів та можливостей. З огляду на те, що поділ моделі запису та моделі читання можна розмістити за допомогою абсолютно різних технологій як оптимізації продуктивності.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.