Как стать автором
Обновить

Как выбрать архитектуру для роста бизнеса: микросервисы или событийно-ориентированная модель?

Уровень сложностиПростой
Время на прочтение3 мин
Количество просмотров5.2K
Всего голосов 4: ↑3 и ↓1+2
Комментарии22

Комментарии 22

  1. Пользователь оформил заказ → сервис заказов сохранил данные в базе.

Не сохранил, ибо кончилось место на диске - заказ потерялся.

  1. Событие «заказ создан» отправилось в шину.

Отправилось, но не принялось, так как из-за повышенной нагрузки на сеть произошёл таймаут.

  1. Сервис платежей подписан на это событие → списал деньги.

Не списал, так как банковский сервис, через который он работает, в данный момент не доступен.

  1. Сервис логистики подписан → передал информацию на склад.

Не передал, так как склад сейчас оффлайн из-за перебоев с интернетом.

  1. Сервис уведомлений подписан → отправил email клиенту.

Не отправил, так как в момент отправки уведомления был перезагруен.

А вот как бы всё это выглядело в реактивной архитектуре:

  1. Пользователь оформил заказ → данные уже сохранены у него в локальной базе.

  2. Локальная база в конечном счёте синхронизируется с серверами.

  3. Сервис платежей видит неоплаченный заказ → пытается списать деньги, пока не получится.

  4. Склад видит оплаченный, но не доставленный заказ → готовит его к выдаче.

  5. Сервис уведомлений видя разницу между тем, что пользователь уже видел, и тем, что ещё нет → отправляет push уведомление.

А можно гдето почитать об этом подходе? На запрос Реактивная Архитектора - выдает очень общий список пожеланий к работе системы - Reactive Manifesto.

Конкретнее

как на шаге #3 сервис видя что в базе есть запись уверен что только он один ее прочитал и потом записал? Что если есть несколько экземпляров сервиса #3.

Сервис #3 мониторит БД SQL запросом? Тоесть PULL, или он получает извещение о том что строка в БД изменилась - PUSH?

Заранее спасибо

Разве что тут. Манифест тот о чём-то другом.

Каждый экземпляр отвечает за свой диапазон идентификаторов и никак не влияет на остальные экземпляры.

Многие СУБД предоставляют "живые запросы", что позволяет получать пуши при изменении в базе.

Вполне допускаю, что существуют СУБД, где это не надёжно. Ко многим это прикручивали костылями в последний момент.

  1. Пользователь оформил заказ → данные уже сохранены у него в локальной базе.

Не сохранены - у пользователя кончилось место. Что раз в 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. Сервис платежей должен пытаться списать деньги до тех пор пока не:

а) словит бан от провайдера банкинга

б) задудосит провайдера банкинга

?

  1. В конечном счёте это файлик на диске в обоих случаях.

  2. Пользователь не будет повторять заказ, так как он уже создан и находится в статусе "ещё не принят в обработку".

  3. Когда получен "ack" событие "доставлено". Когда "ack" не получен, событие хоть и отправлено, но не факт, что доставлено.

  4. Тут вы сами решаете по какой стратегии работать со сторонним сервисом, а не ваши пользователи.

Не понял почему в заголовке ИЛИ.

Микросервисная архитектура - это с моей точки зрения про структуру системы, статика.

Событийно-ориентированная - про порядок взаимодействия, динамика.

Одно не исключает другого, эти вещи разного порядка.

Звучит как "вам яблоко большое или красное"

Событийная модель и микросервисы полностью перпендикулярны друг другу. Между ними нельзя ставить ИЛИ.

Кстати, реактивная модель прекрасно работает с событиями.

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

Заголовок кликюейтный.

Сравнение теплого и мягкого. Управление логикой и организацию противопоставить - сильная заявка.

Давайте противопоставим оркестрацию и монолит?

Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации