Mensageria é uma das habilidades que separam um backend Kotlin simples de um backend pronto para produção. Em sistemas pequenos, uma API REST com banco de dados resolve muita coisa. Mas conforme o produto cresce, surgem operações que não precisam acontecer no mesmo request: envio de e-mail, atualização de estoque, geração de nota, conciliação de pagamento, processamento de imagem, integração com parceiros e publicação de eventos para analytics.
É nesse ponto que entram Kafka, RabbitMQ, SQS e outros brokers. Para quem trabalha com Kotlin, o tema é especialmente interessante porque a linguagem combina bem com processamento assíncrono, coroutines, Flow e frameworks backend como Spring Boot e Ktor. Este guia mostra como pensar mensageria em Kotlin em 2026, sem transformar toda aplicação em uma arquitetura distribuída difícil de operar.
O que é mensageria no backend?
Mensageria é a comunicação entre partes do sistema por meio de mensagens enviadas a um broker. Em vez de um serviço chamar outro diretamente e esperar a resposta, ele publica uma mensagem dizendo que algo aconteceu ou pedindo que uma tarefa seja executada.
Um exemplo comum em e-commerce:
- A API recebe um pedido.
- O pedido é salvo no banco.
- A aplicação publica o evento
PedidoCriado. - Um consumidor envia e-mail de confirmação.
- Outro consumidor atualiza estoque.
- Outro consumidor gera dados para recomendação.
O request principal não precisa esperar tudo isso terminar. O usuário recebe uma resposta rápida, e o restante do trabalho acontece em background.
Esse modelo é útil para reduzir acoplamento, absorver picos de tráfego e aumentar resiliência. Também exige disciplina: mensagens podem duplicar, chegar fora de ordem, falhar temporariamente ou ser reprocessadas depois de minutos.
Kafka, RabbitMQ ou fila simples?
A primeira decisão técnica é entender o tipo de problema. Nem todo uso de mensageria precisa de Kafka.
RabbitMQ costuma funcionar muito bem para filas de trabalho, roteamento flexível e processamento assíncrono clássico. Se você tem tarefas que precisam ser consumidas por workers, com confirmação de processamento e retentativas, RabbitMQ é uma escolha direta.
Kafka brilha quando o sistema precisa de stream de eventos, alto volume, retenção por tempo, replay e múltiplos consumidores independentes. Ele é comum em plataformas de dados, antifraude, observabilidade, integrações de larga escala e arquiteturas event-driven.
SQS, Pub/Sub e serviços gerenciados são ótimas opções quando o time quer reduzir operação. Em muitos produtos brasileiros rodando na AWS, SQS é suficiente para filas assíncronas, enquanto Kafka fica reservado para cenários em que retenção, replay e throughput realmente importam.
Uma regra prática:
- Use fila simples para tarefas assíncronas.
- Use RabbitMQ quando você precisa de roteamento, acknowledgements e workers previsíveis.
- Use Kafka quando eventos são parte do produto e precisam ser reprocessados por vários consumidores.
Kotlin combina com consumidores assíncronos
Kotlin ajuda porque permite escrever consumidores de forma expressiva sem abandonar a JVM. Você pode usar bibliotecas Java maduras e ainda modelar mensagens com data class, validação explícita e serialização type-safe.
Um evento simples pode ser representado assim:
import kotlinx.serialization.Serializable
@Serializable
data class PedidoCriadoEvent(
val pedidoId: String,
val clienteId: String,
val valorTotal: Long,
val criadoEm: String,
val versao: Int = 1
)
O campo versao parece detalhe, mas evita dor quando o contrato evolui. Em mensageria, produtores e consumidores nem sempre são implantados ao mesmo tempo. Mensagens antigas podem continuar no broker enquanto o código novo já está em produção.
Exemplo com Spring Boot e Kafka
Spring Boot segue sendo uma das escolhas mais fortes para backend Kotlin enterprise. Com Spring Kafka, um consumidor fica enxuto:
@Service
class PedidoCriadoConsumer(
private val notaFiscalService: NotaFiscalService,
private val objectMapper: ObjectMapper
) {
private val logger = LoggerFactory.getLogger(javaClass)
@KafkaListener(topics = ["pedido-criado"], groupId = "notas-fiscais")
fun consumir(payload: String) {
val evento = objectMapper.readValue(payload, PedidoCriadoEvent::class.java)
logger.info("Processando pedido criado: pedidoId={}", evento.pedidoId)
notaFiscalService.emitirNota(
pedidoId = evento.pedidoId,
clienteId = evento.clienteId,
valorTotal = evento.valorTotal
)
}
}
O exemplo é propositalmente simples. Em produção, você adicionaria validação, tratamento de erro, métricas, tracing e idempotência. O ponto é que Kotlin não exige um modelo especial: ele aproveita o ecossistema Java, mas deixa o código mais compacto.
Se você já usa monólito modular com Kotlin e Spring, eventos internos podem ser um passo intermediário antes de introduzir Kafka. Primeiro modele eventos dentro do processo; depois promova para mensageria externa quando existir necessidade operacional real.
Exemplo com RabbitMQ e workers Kotlin
RabbitMQ é muito usado para filas de trabalho. Imagine uma aplicação que precisa processar imagens enviadas por usuários. A API publica uma mensagem e workers consomem.
Um worker Kotlin pode seguir esta ideia:
class ProcessarImagemWorker(
private val canal: Channel,
private val processador: ProcessadorImagem,
private val json: Json
) {
fun iniciar() {
canal.basicConsume("imagens.processar", false) { _, delivery ->
val mensagem = json.decodeFromString<ImagemRecebidaEvent>(
delivery.body.decodeToString()
)
try {
processador.processar(mensagem.imagemId)
canal.basicAck(delivery.envelope.deliveryTag, false)
} catch (erro: Exception) {
canal.basicNack(delivery.envelope.deliveryTag, false, true)
}
}
}
}
O detalhe importante é o basicAck. A mensagem só é removida da fila quando o processamento termina com sucesso. Se o worker cair no meio, RabbitMQ pode entregar a mensagem novamente.
Por isso, qualquer consumidor precisa ser idempotente: processar a mesma mensagem duas vezes não pode quebrar o sistema. Você pode garantir isso salvando uma chave única de processamento, usando constraints no banco ou checando o status antes de executar uma ação externa.
Onde entram coroutines?
Coroutines são úteis em consumidores que fazem I/O: chamadas HTTP, banco de dados, storage, APIs externas e serviços internos. Elas permitem concorrência com código legível, especialmente quando combinadas com coroutineScope, supervisorScope e limites claros de paralelismo.
Mas cuidado: não use coroutines para esconder ausência de backpressure. Se um consumidor puxa 10 mil mensagens e dispara 10 mil coroutines sem limite, você só move o gargalo para outro lugar.
Prefira um modelo com paralelismo controlado:
val dispatcher = Dispatchers.IO.limitedParallelism(16)
suspend fun processarLote(mensagens: List<Mensagem>) = coroutineScope {
mensagens.map { mensagem ->
async(dispatcher) {
processarMensagem(mensagem)
}
}.awaitAll()
}
Esse limite precisa refletir banco, APIs externas e CPU disponível. O número certo não vem da linguagem; vem de medição.
Boas práticas para produção
Mensageria em Kotlin exige mais arquitetura do que sintaxe. Antes de publicar o primeiro evento, defina alguns contratos:
- Nome do evento: prefira passado, como
PedidoCriado,PagamentoAprovadoouClienteAtualizado. - Schema versionado: inclua
versaoou use um schema registry quando o volume justificar. - Idempotência: consumidores precisam tolerar mensagens duplicadas.
- Dead letter queue: mensagens que falham repetidamente devem ir para uma fila de análise, não travar o fluxo principal.
- Observabilidade: registre
messageId,correlationId, nome do tópico/fila e tempo de processamento. - Retentativas com cuidado: retry infinito pode derrubar dependências externas e aumentar o incidente.
Também vale separar evento de domínio de comando técnico. PedidoCriado descreve algo que aconteceu. EnviarEmailConfirmacao é uma instrução para um worker. Os dois modelos são válidos, mas misturá-los sem clareza gera acoplamento confuso.
Como isso aparece em vagas Kotlin
Mensageria aparece com frequência em vagas backend Kotlin porque empresas brasileiras usam a linguagem em sistemas de pagamento, varejo, fintech, logística, delivery e plataformas internas. Em muitos anúncios, os requisitos vêm como “Kafka”, “RabbitMQ”, “SQS”, “arquitetura orientada a eventos” ou “microsserviços”.
Para carreira, isso significa que estudar mensageria tem retorno claro. Um desenvolvedor que já domina Kotlin para backend, testes com Kotlin e observabilidade sobe de nível quando consegue explicar trade-offs entre REST síncrono, fila assíncrona e event streaming.
Se você também acompanha outras linguagens backend, vale comparar com Go, que é muito usado em consumidores leves e serviços de infraestrutura. Kotlin, por outro lado, costuma vencer quando o time já está no ecossistema JVM e quer produtividade com type safety forte.
Roteiro prático de estudo
Para aprender sem se perder, siga uma ordem simples:
- Crie uma API Kotlin que publica mensagens após salvar uma entidade.
- Implemente um worker que consome a fila e atualiza outra tabela.
- Adicione retry, dead letter queue e logs estruturados.
- Simule duplicidade e garanta idempotência.
- Meça throughput, latência e consumo de memória.
- Só depois teste Kafka, particionamento, consumer groups e replay.
Esse caminho é mais útil do que começar por uma arquitetura enorme. O valor está em entender o comportamento operacional: o que acontece quando o broker cai, quando o consumidor falha, quando uma mensagem é inválida ou quando um deploy muda o schema.
Conclusão
Kotlin é uma excelente escolha para sistemas com mensageria porque combina expressividade, JVM, bibliotecas maduras e concorrência moderna. Kafka, RabbitMQ e filas gerenciadas resolvem problemas diferentes; a melhor decisão depende do volume, do nível de acoplamento desejado, da necessidade de replay e da capacidade operacional do time.
Para a maioria dos projetos, o melhor começo é pequeno: uma fila para tarefas assíncronas, contratos bem definidos, idempotência desde o primeiro dia e observabilidade suficiente para depurar falhas. Depois, quando eventos virarem parte central do produto, Kafka e event streaming entram com muito mais clareza.
Quem domina esse conjunto deixa de ser apenas “dev Kotlin” e passa a atuar como desenvolvedor backend capaz de desenhar sistemas que aguentam produção.