Комментарии 22
Пользователь оформил заказ → сервис заказов сохранил данные в базе.
Не сохранил, ибо кончилось место на диске - заказ потерялся.
Событие «заказ создан» отправилось в шину.
Отправилось, но не принялось, так как из-за повышенной нагрузки на сеть произошёл таймаут.
Сервис платежей подписан на это событие → списал деньги.
Не списал, так как банковский сервис, через который он работает, в данный момент не доступен.
Сервис логистики подписан → передал информацию на склад.
Не передал, так как склад сейчас оффлайн из-за перебоев с интернетом.
Сервис уведомлений подписан → отправил email клиенту.
Не отправил, так как в момент отправки уведомления был перезагруен.
А вот как бы всё это выглядело в реактивной архитектуре:
Пользователь оформил заказ → данные уже сохранены у него в локальной базе.
Локальная база в конечном счёте синхронизируется с серверами.
Сервис платежей видит неоплаченный заказ → пытается списать деньги, пока не получится.
Склад видит оплаченный, но не доставленный заказ → готовит его к выдаче.
Сервис уведомлений видя разницу между тем, что пользователь уже видел, и тем, что ещё нет → отправляет push уведомление.
А можно гдето почитать об этом подходе? На запрос Реактивная Архитектора - выдает очень общий список пожеланий к работе системы - Reactive Manifesto.
Конкретнее
как на шаге #3 сервис видя что в базе есть запись уверен что только он один ее прочитал и потом записал? Что если есть несколько экземпляров сервиса #3.
Сервис #3 мониторит БД SQL запросом? Тоесть PULL, или он получает извещение о том что строка в БД изменилась - PUSH?
Заранее спасибо
А что в данном случае имеется в виду под термином «локальная база данных»?
Пользователь оформил заказ → данные уже сохранены у него в локальной базе.
Не сохранены - у пользователя кончилось место. Что раз в 1000 вероятнее, чем кончившееся место на выделенном сервисе, обмазанном мониторингом.
Вся описанная схема с eventual consistency подойдет разве что для написания и синхронизации заметок, для задачи покупки - это отвратительный UX.
Если у пользователя настолько закончилось место, то он даже не дойдёт до оформления заказа.
Да-да, расскажите мне про прекрасный UX с потерей заказа из-за недоступности сервера.
Это все очень просто лечится.
Делаешь master-slaves бд.
Делаешь транзакционные запросы в симфонии между событиями.
При сбое одного запроса и нескольких безуспешных траев - откатываешь события обратно. Выводишь юзеру ошибку, что мол так и так, к сожалению обработать платёж не вышло.
Хотя с платежкой наверное лучше отдельный оркестратор сделать - для стабильности, но это как редкие исключения должно быть, так как он все таки отдельные сценарии контролит.
Профит.
Человек в поезде едет и связь постоянно рвётся. До вашей симфонии запросы даже не доходят с первого раза. А пользователь устал уже 10 раз вручную перезапускать оформление заказа. Его вообще не должно волновать где что временно не доступно: "у вас там что-то сломалось? ОК, я подожду, напишите как всё починится и заказ будет оформлен"
Могу ошибаться, но возникает такое чувство, что Вы считаете, что если сообщение не было обработано сервисом (проблемы с сетью или железом), то оно не будет обработано им после восстановления к штатному режиму работы.
Гарантию доставки в шину обмена сообщений обеспечивает transactional outbox. Сами шины обмена сообщений гарантируют at least once delivery. Когда сервис вернётся в рабочий режим он продолжит работу с той точки, где он закончил, даже если он упал где-то в середине обработки сообщения. Для этого и нужна идемпотентность, чтобы убедиться, что повторная обработка сообщения на приведет к сбоям в логике работы приложения.
Так что все будет в порядке, и результат будет такой же, каким Вы себе его представляете в Вашей интерпретации Реактивной Архитектуре.
Это называется "материализация событий" - превращение событий в записи базы данных, за которыми можно наблюдать, идемпотентно синхронизировать и вот это вот всё. Чем раньше вы откажетесь от событий, тем меньше нужно будет костылей с persistent transactional outbox, persistent message queue и тп.

Я говорил в целом про любые типы сообщений, не только события. Команды это тоже сообщения. Команду так же можно принять и обработать позже.
Приведенная схема напоминает схему репликации баз данных. Только вместо классической модели polling представлена модель pushing. Выбор зависит от задачи: если нужно получить весь лог, то polling , елси нужны только cамые последние записи, хватит pushing.
В данном случае Event Log реализует паттерн transactional outbox. Записывать данные в специальную таблицу Outbox не обязательно; главное — обеспечить гарантию транзакционности. Event Log даже является более предпочтительным вариантом, поскольку это нативный инструмент базы данных.
То есть сервер может получить команду PlaceOrderCommand и сохранить ее в таблице Commands для последующей обработки. После обработки команды можно сгенерировать событие OrderPlacedEvent и записать его в таблицу Events. Если требуется представление заказа, то можно либо консистентно материализовать состояние заказа в таблице Orders в одной транзакции с записью события OrderPlacedEvent, либо обновить таблицу Orders позже, обеспечивая консистентность "в конечном итоге".
На самом деле, ваша идея о хранении данных на клиенте с последующей идемпотентной синхронизацией действительно рабочая и может рассматриваться как опция в некоторых сценариях. Заметки функционируют именно так. Однако в случае с онлайн-заказами я бы лично не стал использовать такой подход.
1. Что подразумевается под "локальной базой", например для нативного и веб приложения?
2. Как разрешить "ситуацию/конфликт" в следующем кейсе с локальной базой?
Во время создания заказа в нативной версии мобильного приложения сервис обработки заказов лежит (исходят из Вашего комментария - сохраняем данные в локальную базу), из-за чего клиент пробует сделать тоже самое через веб - аналогично терпит неудачу (опять сохраняем в локальную базу). Теперь сервис оживает и получает сразу 2 заказа (которые с точки зрения клиента одинаковые -> дубль стоит откинуть, но для сервиса это 2 разных заказа)
3. Приведите пример когда возможен случай: "событие отправилось в шину но не принялось из-за таймаута". В моем понимании - событие считает отправленным тогда и только тогда, когда от шина получен "ack"
4. Сервис платежей должен пытаться списать деньги до тех пор пока не:
а) словит бан от провайдера банкинга
б) задудосит провайдера банкинга
?
В конечном счёте это файлик на диске в обоих случаях.
Пользователь не будет повторять заказ, так как он уже создан и находится в статусе "ещё не принят в обработку".
Когда получен "ack" событие "доставлено". Когда "ack" не получен, событие хоть и отправлено, но не факт, что доставлено.
Тут вы сами решаете по какой стратегии работать со сторонним сервисом, а не ваши пользователи.
Не понял почему в заголовке ИЛИ.
Микросервисная архитектура - это с моей точки зрения про структуру системы, статика.
Событийно-ориентированная - про порядок взаимодействия, динамика.
Одно не исключает другого, эти вещи разного порядка.
Звучит как "вам яблоко большое или красное"
Событийная модель и микросервисы полностью перпендикулярны друг другу. Между ними нельзя ставить ИЛИ.
Кстати, реактивная модель прекрасно работает с событиями.
Открываем рандомную книгу про архитектуру приложений и увидим примерно следующее, что архитектура приложения зависит от объема нагрузки и ее характера. То есть заложить сразу и предсказать, что вот это решение будет у нас хорошо работать в дальнейшем, довольно сложно. Плюс постоянно появляются новые подходы и инструменты. Понятно, что архитектурные изменения в работающем большом приложении это долго и дорого, но по мере развития приложения в какой-то момент дорабатывать придется.
Заголовок кликюейтный.
Сравнение теплого и мягкого. Управление логикой и организацию противопоставить - сильная заявка.
Давайте противопоставим оркестрацию и монолит?
Как выбрать архитектуру для роста бизнеса: микросервисы или событийно-ориентированная модель?