Arquitetura produtor consumidor e decompositor no dia a dia
A maioria dos projetos que vejo travar em produção tem um problema simples demais: alguém tentou encaixar processamento assíncrono em código síncrono sem planejar a fila. Eu já passei por isso três vezes nos últimos cinco anos. A arquitetura produtor consumidor e decompositor resolve isso quando aplicada com disciplina, mas estraga tudo se você ignorar os detalhes. Vou explicar como funciona na prática, não da forma como os livros ensinam. Primeiro o mecanismo. Você tem produtores que geram eventos ou tarefas. Cada produto entra em uma fila. Um decompositor lê da fila e quebra a tarefa em subtarefas menores. Os consumidores processam essas subtarefas em paralelo.
por que a combinação produtor consumidor e decompositor funciona
O separador entre produção e consumo é o que mantém o sistema respirando. Quando o produtor não conhece o consumidor, você consegue trocar a lógica de processamento sem tocar na geração de dados. Isso é importante porque dados chegam em formatos diferentes e ninguém sabe ainda como vão ser usados daqui a seis meses. Eu implementei isso em um pipeline de processamento de logs de transações financeiras. O produtor era um serviço Java que recebia eventos via REST. A fila ficava no Kafka. O decompositor liu mensagens e as transformava em jobs do Spark. Os consumidores eram containers Docker rodando em ECS. Funcionou durante dois anos até migramos para um esquema diferente.
O problema real aparece quando alguém coloca o decompositor como parte do mesmo ciclo do produtor. Aí você perde isolamento e a fila deixa de ser um amortecedor. Já vi um time colocar o decompressor dentro do serviço produtor para economizar uma linha de código. O resultado foi deadlock em 48 horas porque o consumidor não conseguia acompanhar a taxa de geração.
implementação prática passo a passo
Comece definindo o contrato do produto. A mensagem precisa ter identificação única, timestamp, tipo de evento e payload. Sem identificação única, duplicatas viram um pesadelo. Timestamp é obrigatório para ordenação. Tipo de evento separa os consumidores que processam cada coisa. Segundo passo: escolha a fila. Kafka funciona bem para alto throughput. RabbitMQ é mais simples para carga menor. SQS é útil se você já está no ecossistema AWS e não quer gerenciar infraestrutura. Cada um tem trade-off de consistência e latência que você precisa medir antes de decidir.
👉 Clique no botão abaixo para saber mais sobre o assunto!
Terceiro passo: escreva o decompositor como um processo independente. Ele lê da fila, transforma o produto em subtarefas e as publica em outra fila ou diretamente nos consumidores. A transformação deve ser determinística. Se o mesmo produto entrar, o mesmo conjunto de subtarefas deve sair. Caso contrário você terá processamento duplicado ou perdido dependendo de como lidar com retransmissões. Quarto passo: os consumidores devem ser stateless sempre que possível. Se um consumidor precisar manter estado, use uma store externa com chaves derivadas do id da subtarefa. Isso permite scale horizontal sem colisões.
casos de borda que ninguém conta
Dead letters são o maior problema prático. Produtos que falham várias vezes precisam de um destino separado. Eu configurei uma fila de dead letter e um consumidor monitor que envia alerta no Slack. Sem isso, produtos defeituosos acumulam e bloqueiam a fila inteira. O outro problema é ordem relativa. Produtores múltiplos podem enviar mensagens que precisam ser processadas em sequência. Kafka preserva ordem por partição. Se você partitionar por id de cliente, garante ordenação dentro do mesmo cliente. Trocar a partição depois de algum tempo gera inconsistências. Eu errei isso uma vez e perdi três dias corrigindo dados.
Também precisa lidar com consumers que morrem durante o processamento. O modelo at-least-once é mais seguro que at-most-once para a maioria dos casos. Confirmação manual depois do processamento concluído evita perda de dados. Confirmação automática antes do processamento causa perda quando o consumer cai no meio. Um detalhe que causa dor de cabeça é o backpressure. Se o consumidor for mais lento que o produtor, a fila cresce. Você precisa definir tamanho máximo e ação quando estourar. Descartar eventos antigos raramente é aceitável. Rejeitar novos produtos até a fila baixar é a opção mais segura, mas impacta o produtor. Eu usei uma fila com tamanho configurável e um mecanismo de rejeição com retry exponencial no produtor. Reduziu incidentes em cerca de 70% no nosso caso.
quando não usar essa arquitetura
Se seu sistema processa menos de mil eventos por minuto e a lógica é simples, o overhead de uma fila dedicada pode não valer a pena. Um scheduler com workers basta. Se precisar de latência abaixo de 50ms, o round-trip pela fila adiciona delay significativo. Nesse caso, chamadas diretas ou pub/sub em memória funcionam melhor. Também não use se sua equipe não tiver experiência com monitoramento de filas. Produtores que enviam e esquecem sem métricas geram buracos invisíveis. Você descobre o problema quando o dashboard mostra zero eventos há duas horas e o banco de dados já está inconsistente.
O padrão produtor consumidor e decompositor é sólido quando aplicado com clareza. Defina contratos, isole processos, monitore filas e prepare-se para dead letters. O resto é ajuste de tamanho de batches e número de consumidores conforme a carga real.