Идея: функция отдельно от брокера
Spring Cloud Stream позволяет писать обработчики событий так, чтобы код не зависел от конкретного брокера. Приложение описывает, что делать с сообщением, а связь с Kafka или RabbitMQ берёт на себя binder — адаптер под конкретный брокер. Сменить брокер можно заменой зависимости и конфигурации, не трогая бизнес-код.
У абстракции есть предел: модель общая для всех брокеров, поэтому специфичные возможности (ручное управление offset в Kafka, сложная маршрутизация RabbitMQ, задержанная доставка) приходится настраивать через свойства конкретного binder, а иногда они недоступны вовсе.
Функциональная модель
В современных версиях обработчики — это обычные бины функциональных типов Java: Supplier публикует сообщения, Function принимает и отдаёт результат дальше, Consumer только принимает. Имя бина задаёт имя привязки (binding), а в конфигурации привязка связывается с топиком или очередью.
@Bean
Consumer<OrderPaid> bill() {
return event -> billing.handle(event);
}
spring:
cloud:
stream:
bindings:
bill-in-0:
destination: orders.paid # топик или exchange
group: billing # группа потребителей
Код получается компактным, но связь между функцией и топиком живёт в конфигурации. Чтобы через полгода было понятно, кто что читает, имена привязок и топиков стоит держать единообразными.
Destination и group
Destination — логическое имя места доставки: топик Kafka или exchange RabbitMQ. Group — группа потребителей: экземпляры одного сервиса с одной группой делят сообщения между собой, а разные группы получают каждая свою копию потока. Для Kafka группа становится group.id и хранит свой прогресс чтения.
Частая ошибка — не указать группу. Тогда потребитель становится анонимным: при каждом запуске получает новую временную группу, не сохраняет прогресс и после перезапуска читает только новые сообщения. Для боевого сервиса группа указывается всегда.
Партиционирование
Чтобы события одной сущности обрабатывались по порядку, их нужно направлять в одну партицию. В Cloud Stream для этого задают выражение ключа партиционирования (partition-key-expression, например идентификатор заказа) и число партиций.
Абстракция не отменяет понимания брокера: в Kafka порядок гарантирован только внутри партиции, а в RabbitMQ партиции эмулируются набором очередей с маршрутизацией — и семантика у них другая.
Когда лучше без Cloud Stream
Если сервису нужны тонкие возможности конкретного брокера — ручной коммит offset, транзакции Kafka, собственная стратегия повторов и DLT, — напрямую Spring Kafka или Spring AMQP обычно понятнее: меньше слоёв, проще отладка, документация брокера применима без перевода.
Cloud Stream хорош там, где у компании много однотипных сервисов-обработчиков и важно единообразие. Но он не заменяет знания брокера: ошибки с порядком, группами и повторной доставкой всё равно придётся понимать на уровне Kafka или RabbitMQ.
Как отвечать на собеседовании
Объясните идею binder: бизнес-код отдельно, брокер — в конфигурации. Покажите функциональную модель (Supplier, Function, Consumer) и как привязка связывает функцию с топиком.
Обязательно назовите ловушку с отсутствующей группой и роль ключа партиционирования для порядка. Закончите границей применимости: для типовых обработчиков Cloud Stream удобен, а когда нужны специфичные возможности брокера, честнее использовать Spring Kafka или Spring AMQP напрямую.