Как обеспечивается транзакционность в микросервисной архитектуре?

«Как обеспечивается транзакционность в микросервисной архитектуре?» — вопрос из категории Архитектура, который задают на 10% собеседований Java Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

В микросервисах классические ACID-транзакции через границы сервисов невозможны. Вместо этого применяются паттерны BASE (Basically Available, Soft state, Eventually consistent) и следующие подходы:

1. Паттерн Saga: Последовательность локальных транзакций, где каждый следующий шаг зависит от успеха предыдущего. При неудаче выполняются компенсирующие транзакции.

// Пример реализации Saga с событиями
@Service
public class OrderSagaService {

    @Transactional
    public void createOrder(Order order) {
        // 1. Локальная транзакция: создать заказ в статусе PENDING
        orderRepository.save(order);

        // 2. Отправить событие для резервирования товара
        eventPublisher.publish(new ReserveItemsEvent(order.getId()));
    }

    // Обработчик успешного резервирования
    @TransactionalEventListener
    public void handleItemsReserved(ItemsReservedEvent event) {
        // 3. Локальная транзакция: списать деньги
        paymentService.charge(event.getOrderId());
        // Отправить событие об успешном списании
    }

    // Компенсирующее действие при ошибке оплаты
    @TransactionalEventListener
    public void handlePaymentFailed(PaymentFailedEvent event) {
        // Откат: освободить зарезервированные товары
        inventoryService.releaseItems(event.getOrderId());
        // Обновить статус заказа на FAILED
        orderRepository.updateStatus(event.getOrderId(), OrderStatus.FAILED);
    }
}

2. Паттерн Outbox: Гарантирует доставку событий в рамках локальной транзакции.

-- В рамках одной транзакции с бизнес-данными
BEGIN TRANSACTION;
  INSERT INTO orders (id, amount) VALUES (123, 100.00);
  INSERT INTO outbox (id, aggregate_id, event_type, payload) 
  VALUES (gen_random_uuid(), 123, 'OrderCreated', '{"amount":100}');
COMMIT;
-- Отдельный процесс (CDC, polling) читает outbox и публикует в брокер

3. Паттерн TCC (Try-Confirm-Cancel):

// Фаза Try: предварительное резервирование ресурсов
paymentService.reserve(orderId, amount); // Резервируем, но не списываем
inventoryService.reserve(orderId, items); // Резервируем товары

// Фаза Confirm: фиксация (идемпотентная)
paymentService.confirm(orderId); // Списание зарезервированных средств
inventoryService.confirm(orderId); // Подтверждение резерва

// Фаза Cancel: отмена (идемпотентная)
paymentService.cancel(orderId); // Отмена резерва
inventoryService.cancel(orderId); // Освобождение товаров

4. Технологии и инструменты:

  • Брокеры сообщений: Kafka (с идемпотентным продюсером и exactly-once семантикой), RabbitMQ
  • Оркестраторы: Camunda, Temporal, AWS Step Functions
  • Мониторинг: Distributed tracing (Jaeger, Zipkin) для отслеживания потока транзакции

5. Критические требования:

  • Идемпотентность: Все операции должны безопасно повторяться
  • Компенсация: Для каждого действия должен быть определен компенсирующий action
  • Мониторинг: Необходима видимость состояния распределенной транзакции
  • Таймауты и ретраи: Стратегии обработки временных сбоев