🪓Сага о распределенных транзакциях🪓
В прошлом посте я кратко сказал про атомарность транзакций. Теперь хочу поговорить, как ее можно добиться в распределенных системах. В 1С мы привыкли, что все операции по какому-либо бизнес-событию происходят в одной системе – монолите. Но в современном ИТ очень много работает на микросервисной архитектуре: совокупности разрозненных систем, между которыми идет обмен данными.
Рассмотрим классическую ситуацию, когда человек оформляет заказ на сайте. После нажатия кнопки ✅Заказать на той стороне должно произойти множество действий: складской сервис зарезервировать товар, другой – списать или начислить бонусы, третий – записать заказ в историю заказов и т.д. Что делать, если один из сервисов сообщил об отказе, в то время как другие уже выполнили операцию? Например, бонусы списались, заказ в историю попал, но на складе вдруг товара не оказалось?
Дальше пойдет много поэтических терминов. Для решения этой проблемы обычно используется паттерн Сага (Saga) ⚔️. Идея его в том, что последовательность разрозненных транзакций в каждом микросервисе как рассказы об отдельных подвигах складываются в единую сагу о приключениях нашего бизнес-события. Если в каком-то из сервисов произошел откат транзакции, должны быть вызваны компенсирующие операции (откат, сторнирование) во всех уже пройденных сервисах.
Но как обеспечить эту согласованность? Тут есть два основных подхода:
🎻 Оркестрация (Orchestration). В данном случае выделяется специальный сервис оркестратор-дирижер, который следит за полным выполнением большой транзакции. С него начинается выполнение операции, он знает все шаги ее выполнения, следит за статусами, отправляет команды отмены нужным точкам при необходимости.
💃 Хореография (Choreography). Здесь нет начальника, каждый сам отвечает за свои действия, танцует свою партию. Сервисы общаются сообщениями между собой через какое-то общее пространство (шина, Kafka, RabbitMQ). Один сервис выполнил, написал
БонусыСписаны, другой написал – Ахтунг:ТоварНеЗарезвирован, все другие сервисы получат это сообщение и обработают как необходимо.Вот такие подходы есть в микросервисах, но вернемся к 1С. Можно привести пример распределенной транзакции, когда надо записать документ в базе и одновременно выполнить операцию в другой системе по REST API. Тривиальным решением может служить следующий кусок кода:
НачатьТранзакцию();
Попытка
ДокументОбъект.Записать();
Ошибки = ВыполнитьОперациюПоAPI(ДокументОбъект.Ссылка);
Если ЗначениеЗаполнено(Ошибки) Тогда
ВызватьИсключение Ошибки;
КонецЕсли;
ЗафиксироватьТранзакцию();
Исключение
ОтменитьТранзакцию();
КонецПопытки;
Главное в этом коде, что выполнение операции по API стоит после записи документа в нашей БД. Ведь отменить событие в другой системе при ошибке нашей записи будет намного сложнее, нежели запись у нас. Конечно, здесь есть проблема, что мы открываем транзакцию и начинаем длительную операцию обращения к внешним ресурсам. Из-за этого транзакция может надолго зависнуть, что приведет к потере производительности.
Если подходить к проблеме комплексно, когда у вас сервисами выступает несколько отдельных конфигураций, то можно прийти к тем же подходам. Например, использовать хореографическую сагу, настроив обмен через Kafka, которая гарантирует доставку сообщений. При проведении документа или ошибки регистрируется сообщение в топик с результатом. Остальные системы регламентным заданием слушают нужные топики и отменяют проведение, если пришла ошибка по данной операции.
А сталкивались ли вы с такими распределёнными транзакциями? Какие решения использовали?
