Есть много решений, которые компрометируют больше, чем мне удобно. Конечно, если ваш вариант использования сложный, такой как перемещение денег между различными банками, более приятные альтернативы могут быть невозможны. Но давайте посмотрим, что мы можем сделать в обычном сценарии, когда использование микросервисов мешает нашим потенциальным транзакциям базы данных.
Вариант 1. Избегайте необходимости транзакций, если это возможно
Очевидный и упомянутый ранее, но идеальный, если мы можем справиться с этим. Действительно ли компоненты принадлежат одному и тому же микросервису? Или мы можем изменить систему (ы) так, чтобы транзакция стала ненужной? Возможно, принятие не транзакционности - самая доступная жертва.
Вариант 2: использовать очередь
Если есть достаточная уверенность в том, что другой сервис преуспеет в том, что мы хотим, мы можем вызвать его через некоторую форму очереди. Элемент в очереди не будет забран до позже, но мы можем убедиться, что элемент в очереди .
Например, скажем, что мы хотим вставить объект и отправить электронное письмо как одну транзакцию. Вместо того, чтобы вызывать почтовый сервер, мы ставим электронную почту в таблицу.
Begin transaction
Insert entity
Insert e-mail
Commit transaction
Очевидным недостатком является то, что нескольким микросервисам потребуется доступ к одной и той же таблице.
Вариант 3: выполняйте внешнюю работу последним, непосредственно перед завершением транзакции
Этот подход основывается на предположении, что совершить транзакцию очень вряд ли удастся.
Begin transaction
Insert entity
Insert another entity
Make external call
Commit transaction
Если запросы не выполняются, внешний вызов еще не состоялся. В случае сбоя внешнего вызова транзакция никогда не фиксируется.
Этот подход имеет ограничения, заключающиеся в том, что мы можем сделать только один внешний вызов, и он должен быть выполнен последним (то есть мы не можем использовать его результат в наших запросах).
Вариант 4: создать вещи в состоянии ожидания
Как указывалось здесь , мы можем иметь несколько микросервисов для создания разных компонентов, каждый в состоянии ожидания, без транзакций.
Любая проверка выполняется, но в определенном состоянии ничего не создается. После того, как все было успешно создано, каждый компонент активируется. Обычно эта операция настолько проста, и вероятность того, что что-то пойдет не так, настолько мала, что мы можем даже предпочесть выполнить активацию без транзакций.
Наибольшим недостатком, вероятно, является то, что мы должны учитывать существование ожидающих пунктов. Любой выбранный запрос должен учитывать, следует ли включать ожидающие данные. Большинство должно игнорировать это. А обновления это совсем другая история.
Вариант 5: разрешить микросервису поделиться своим запросом
Ни один из других вариантов не сделает это за вас? Тогда давайте неортодоксально .
В зависимости от компании это может быть неприемлемо. Я знаю. Это неортодоксально. Если это не приемлемо, идите другим путем. Но если это соответствует вашей ситуации, это решает проблему просто и мощно. Это может быть просто самый приемлемый компромисс.
Существует способ превратить запросы из нескольких микросервисов в простую транзакцию с одной базой данных.
Верните запрос, а не выполняйте его.
Begin transaction
Execute our own query
Make external call, receiving a query
Execute received query
Commit transaction
В сети каждый микросервис должен иметь возможность доступа к каждой базе данных. Имейте это в виду, также в отношении будущего масштабирования.
Если базы данных, участвующие в транзакции, находятся на одном сервере, это будет обычная транзакция. Если они находятся на разных серверах, это будет распределенная транзакция. Код один и тот же независимо.
Мы получаем запрос, включая его тип подключения, его параметры и строку подключения. Мы можем обернуть его в аккуратный исполняемый класс Command, оставляя поток читаемым: вызов microservice приводит к команде, которую мы выполняем, как часть нашей транзакции.
Строка подключения - это то, что дает нам исходный микросервис, поэтому для всех намерений и целей этот запрос все еще считается выполненным этим микросервисом. Мы просто физически направляем его через микросервис клиента. Это имеет значение? Ну, это позволяет нам поместить его в ту же транзакцию с другим запросом.
Если компромисс приемлем, такой подход дает нам прямую транзакционность монолитного приложения в микросервисной архитектуре.