Как обеспечивалась транзакционность при работе с сообщениями (например, в Kafka) в вашем проекте?

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

Ответ

В проекте использовался паттерн «транзакционность базы данных и отправки сообщений» для гарантии согласованности между состоянием БД и отправленными событиями.

Основной подход (Spring Kafka + @Transactional):

@Service
public class OrderService {
    @Transactional // Транзакция управляется Spring
    public void createOrder(Order order) {
        // 1. Сохраняем заказ в БД
        orderRepository.save(order);
        // 2. Отправляем событие в Kafka.
        // Если на предыдущем шаге произойдет исключение, транзакция откатится и сообщение не будет отправлено.
        kafkaTemplate.send("order-events", order.getId(), new OrderCreatedEvent(order));
    }
}

Для работы требуется настройка KafkaTransactionManager и ChainedKafkaTransactionManager (или JpaTransactionManager) для объединения транзакций.

Паттерн Transactional Outbox для повышенной надежности:

  1. Событие сохраняется в ту же БД-транзакцию в специальную таблицу outbox.
  2. Отдельный процесс (например, Debezium или scheduled task) читает из outbox и публикует события в Kafka. Это гарантирует доставку даже при длительных простоях брокера.

Важно: Этот подход обеспечивает «at-least-once» доставку. Для идемпотентности потребителей необходимо использовать уникальные идентификаторы сообщений (idempotent producer).