Вопрос

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

Как мы реализуем бизнес-правила, которые полагаются на множество совокупных корней?

Возьмите, в качестве примера, приложение для бронирования. Это может позволить вам бронировать билеты на концерт, места для фильма или стола в ресторане. Во всех случаях, будет только будет только ограниченное число «предметов» для продажи.

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

На стороне запроса мы можем масштабироваться масштабируемо, и оговорки нанесены в очередь, которая будет обрабатываться асинхронно путем автономного компонента. Сначала, когда мы снимаем команды бронирования из очереди, мы приму их, но в определенное время нам придется начать отклонение отдохнуть.

Как мы узнаем, когда мы достигаем пределов?

Для каждой команды бронирования нам придется запрашивать какой-то магазин, чтобы выяснить, можно ли разместить запрос. Это означает, что нам нужно будет знать, сколько оговорок мы уже получили в то время.

Тем не менее, если хранилище домена является невязким хранилищем данных, например, например, хранение таблицы Windows Azure, мы не можем очень хорошо сделать SELECT COUNT(*) FROM ...

Один вариант будет держать Отдельный совокупный корень Это просто отслеживает текущий счет, как это:

  • AR: Бронирование (кто? Сколько?)
  • AR: Слот события / временного интервала / дата (совокупность)

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

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

Такие сценарии напоминают мне стандартные сценарии «Нет на складе», но разница в том, что мы не можем очень хорошо поместить резервирование на задний порядок. Как только событие будет распродано, он продан, поэтому я не вижу, что будет компенсирующее действие.

Как мы справляемся с такими сценариями?

Это было полезно?

Решение

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

Действительно это сводится к этой проблеме: если вам нужно обновить несколько ресурсов, как вы обеспечите консистенцию, если вторая операция записи не удается?

Давайте представим, что мы хотим написать обновления для ресурса A и Resource B в последовательности.

  1. Ресурс A успешно обновлен
  2. Попытка обновления ресурса B не удается

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

Ключ лежит в явном idempotency.. Отказ В то время как очереди Windows Azure не гарантируют ровно один раз семантика, они гарантируют Хотя бы один раз семантика. Это означает, что перед лицом прерывистых исключений сообщение позже будет воспроизведенный.

В предыдущем сценарии это то, что происходит тогда:

  1. Ресурс A пытается обновить. Однако воспроизведение обнаружено, поэтому состояние A не затронуто. Тем не менее, операция «Написать» преуспевает.
  2. Ресурс B успешно обновляется.

Когда все операции записи являются идемпотентными, возможная согласованность может быть достигнуто с повторы сообщений.

Другие советы

Интересный вопрос и с этим вы прибиваете одну из боли в CQRS.

Упоподают, что Amazon обрабатывает, это связано с бизнес-сценарием с состоянием ошибки, если запрошенные предметы распроданы. Состояние ошибки - это просто уведомить клиента по электронной почте, что товары, запрошенные в настоящее время в наличии, а также предполагаемый день для доставки.

Однако - это не полностью отвечает на ваш вопрос.

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

Сейчас. Можем ли мы сделать это воссталение менее болезненным? Безусловно. Вставляя ключ в нашем распределенном кэше с процентом или количеством элементов в наличии и уменьшением этого счетчика, когда когда-либо будет продан товар. Таким образом, мы могли бы предупредить пользователя до предоставления запроса бронирования, давайте скажем, если осталось только 10% от начального количества предметов, что клиент может не сможет получить вопрос о предмете. Если счетчик на нуле, мы бы просто отказались принять дополнительные запросы на бронирование.

Моя точка зрения:

1) Пусть пользователь узнает, что это запрос, который они делают, и что это может быть отказано 2) сообщить пользователю, что шансы на успех для получения вопроса о предмете низкий

Не совсем точный ответ на ваш вопрос, но именно так я справился с таким сценарием при работе с CQRS.

Etag позволяет оптимистичным параллелизмом, которые вы можете использовать вместо транзакционной блокировки для обновления документа и безопасно обрабатывать потенциальные условия гонки. Смотрите замечания здесь http://msdn.microsoft.com/en-us/library/ddd179427.aspx. для получения дополнительной информации.

История может пойти что-то вроде этого: пользователь A создает событие E с Max Bills Of 2, Etag - 123. Из-за высокого спроса 3 пользователи пытаются приобрести билеты почти в то же время. Пользователь B Создает запрос бронирования B. Пользователь C Создает запрос бронирования C. Пользователь D создает запрос на бронирование D.

Система S принимает запрос бронирования B, читает событие с Etag 123 и изменяет событие, имеющее 1 оставшийся билет, S представляет обновление, включая ETAG 123, который соответствует исходному Etag, поэтому обновление добивается успеха. ETAG сейчас 456. Запрос бронирования утвержден, и пользователь уведомлен, был успешным.

Другая система S2 получает запрос оранжезда C одновременно, поскольку система S была обработкой запроса B, поэтому он также считывает событие, событие с Etag 123 изменяет его на 1 оставшийся билет и попытки обновить документ. На этот раз, однако ETAG 123 не совпадает, поэтому обновление не удается с исключением. Система S2 пытается повторить операцию, повторно прочитав документ, который теперь имеет ETAG 456, и количество 1, поэтому он уменьшает его к 0 и повторно, повторно, с помощью Etag 456.

К сожалению для пользователя Co пользователь C, система S запустила обработку запроса пользователя сразу после пользователя B, а также прочитать документ с Etag 456, но потому что система S будет быстрее, чем система S2, она смогла обновить событие с Etag 456 перед системой S2, так что пользователь D также успешно зарезервировал свой билет. Etag сейчас 789

Таким образом, система S2 снова выходит из строя, дает ей еще один попыток, но на этот раз, когда она читает событие с Etag 789, он видит, что нет доступных билетов и, таким образом, запрещает запрос на бронирование пользователей C.

Как вы уведомите пользователей, что их запросы на бронирование были успешными или нет для вас. Вы можете просто опросить сервер каждые несколько секунд и дождаться обновления статуса бронирования.

Давайте посмотрим на деловую перспективу (я имею дело в подобных вещах - приписанные встречи на свободных слотах) ...

Первое в вашем анализе, который поражает меня, так это то, что нет понятия зарезервированного билета / сиденья / таблицы. Это ресурсы, забронированные.

В случае транзакции вы можете использовать некоторую форму уникальности, чтобы убедиться, что двойное бронирование не произойдет для того же билета / сиденья / таблицы (подробнее http://seabites.wordpress.com/2010/11/11/consistent-indexes-constraints.). Этот сценарий требует синхронной (но все еще одновременно) обработки команды.

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

Давайте вернемся еще немного ...

Когда биллинг также участвует (например, продажа онлайн билетов), я думаю, что весь этот сценарий развивается в Сага в любом случае (билет на резервный билет + билет). Даже без биллинга у вас будет сага (резервная таблица + подтверждение бронирования), чтобы сделать опыт заслуживающим доверия. Так что, хотя вы только масштабируетесь только на одном аспекте бронирования билета / таблица / сиденья (т. Е. Это все еще доступно), «долгожданная» транзакция не завершена, пока я не заплатил за это или пока я не подтвердил его Отказ Компенсация произойдет в любом случае, освобождая билет снова, когда я прерванию транзакции для любой причины. Интересная часть сейчас становится тем, как бизнес хочет иметь дело с этим: возможно, некоторые другие клиенты завершили бы транзакцию, которые мы дали ему / ее тот же билет. В этом случае возврат может стать более интересным, когда двойное бронирование билета / сиденья / таблицы - даже предлагая скидку на следующем / аналогичном мероприятии, чтобы компенсировать неудобства. Ответ находится в бизнес-модели, а не технической модели.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top