BACKEND MODERNO · CAPÍTULO 02

Do pedido
ao pagamento.

Uma mensagem pode esperar.
Uma decisão precisa de evidência.

Mensageria do zero: por que filas existem, como o RabbitMQ entrega, e dois serviços Java conversando por mensagens — com desenhos, fluxos animados, prompts para orientar o agente e provas para conferir.

Pedido 47112 cafés × R$ 18,90R$ 37,80Recebido ≠ aprovado
📖 Antes🏫 Em sala🏠 Em casa🔤 Glossário

📖 Antes da aula

Leia para reconhecer o problema. Nada aqui exige que você já saiba mensageria.

Antes da aula · ponto de partida

O pedido continua. O pagamento ganha seu tempo. [01]

No Cap 01, construímos uma API: um programa que recebe requisições HTTP, aplica regras e guarda pedidos no PostgreSQL, um banco de dados que roda dentro de um container Docker. Até aqui, tudo acontecia dentro de um único programa: o pedido chegava, a regra rodava, o banco salvava, a resposta voltava.

O que já existe: cliente, API de Pedidos e Postgres ClienteInsomnia / navegador HTTP API de Pedidos · porta 8080 · um único programa Controllerrecebe JSON Caso de usoaplica a regra PedidoABERTO · 37,80 tudo acontece dentro da mesma chamada HTTP Postgresno Dockertabela pedidos
O que você já tem. Uma requisição entra, passa pelas camadas e sai com resposta. Hoje o pagamento sai dessa caixa tracejada e vira trabalho de outro programa.

Agora vamos separar dois trabalhos que parecem um só: receber a solicitação de pagamento e processar o pagamento. Eles vão rodar em programas diferentes, em momentos diferentes. Para ligá-los, usaremos uma fila de mensagens.

O Pedido 4711 é nosso apelido no desenho. Na API, cada pedido recebe um UUID (identificador universal único): um código como 89e83d74-29b8-4a52-aaf0-2f33877c9f99. Você usará o ID retornado pelo seu projeto.

Hoje usamos pagamentos simulados: APROVAR e RECUSAR são cenários controlados de aula. Não há banco financeiro, cartão, Pix ou dinheiro real envolvido.

Antes da aula · organização

Uma tarde, uma história verificável [02]

A tarde tem quatro partes. Primeiro entendemos por que filas existem e como o RabbitMQ entrega mensagens — com desenhos e fluxos animados. Depois colocamos a mão no painel, sem código. Só então o agente constrói os dois serviços, e terminamos provocando falhas de propósito.

InícioTempoEtapaPontos
13:3015 minAbertura: retomada e diagnóstico com o agente trabalhando em paralelo03
13:4520 minParte 1 · Por que uma fila: síncrono × assíncrono, a conta da dependência04–05
14:0520 minParte 1 · O que viaja: anatomia da mensagem; comando, evento e consulta06–07
14:2525 minParte 2 · Como o RabbitMQ entrega: peças, exchanges, fila e ack08–10
14:5025 minLab A · RabbitMQ no Docker e primeira fila de treino no painel11–12
15:1515 min☕ Intervalo—
15:3010 minParte 3 · O fluxo completo e o mapa da bancada13–14
15:4020 minContrato, spec e leitura do código Spring AMQP antes de gerá-lo15–16
16:0025 minPedidos publica; bancada no Insomnia; consumidor desligado17–19
16:2525 minPagamentos consome; o resultado volta para Pedidos20–21
16:5025 minParte 4 · Garantias, três confirmações, duplicidade e falhas22–25
17:1515 minDesafio curto e fechamento26–27

Cada etapa prática segue entender → prever → orientar o agente → executar → conferir. O prompt funciona em Claude Code, Codex CLI ou outro agente capaz de trabalhar no projeto. A decisão continua com você.

📓 Abrir caderno livre · Baixar collection do Insomnia · Guia de execução e recuperação

🏫 Em sala

Entenda, preveja, execute e confira

Abertura · juntos · antes de alterar 13:30 · 15 min

Coloque o agente para ler enquanto conversamos [03]

Um diagnóstico leva alguns minutos: o agente precisa ler arquivos e rodar testes. Em vez de esperar parado, dispare o prompt agora e siga para a teoria. Quando voltar ao terminal, a resposta estará pronta para ser revisada.

Queremos um diagnóstico com evidências: versão do Java, banco, endpoints e testes. Uma lista de arquivos não prova que a aplicação funciona; um teste passando, sim.

Prompt 1 · diagnóstico da API
[CONTEXTO] Vou continuar a API de Pedidos do Cap 01 com pagamentos simulados por RabbitMQ.
[TAREFA] Sem criar nem editar arquivos, leia AGENTS.md, CLAUDE.md e as specs relevantes. Identifique endpoints, status do pedido, configuração do Postgres e Docker Compose existente. Execute os testes unitários pelo Maven Wrapper.
[LIMITES] Não instale dependências nem reinicie ou remova containers. Não presuma que concluí as tarefas anteriores.
[ACEITE] Separe funcionando com prova, falhando e ainda ausente. Mostre como criar e consultar um pedido de 2 cafés a 18.90.
[PAUSA] Proponha o menor ponto de retomada e aguarde meu OK antes de editar.

A regra da tarde: cada etapa aprovada vira um commit

Hoje o agente vai mexer em muitos arquivos, em dois serviços. Para não perder o fio, todo prompt a partir do 2 termina com uma linha [COMMIT]: depois que você revisa o diff e dá OK, o agente grava a etapa num commit e faz push para a branch main do seu repositório no GitHub.

Commit é uma foto do projeto guardada no Git da sua máquina, com uma mensagem dizendo o que mudou. Push envia esses commits para o repositório remoto, no GitHub. São coisas diferentes: commit sem push só existe no seu computador.

Ciclo de cada etapa: revisar, commit, push Agente geracódigo + testes Você revisadiff + provas → OK commitfoto local da etapa pushenvia GitHubmain Sem OK, sem commit. Se uma etapa quebrar algo, você volta ao commit anterior em vez de recomeçar.
Um ciclo por prompt. Os commits contam a história da tarde: RabbitMQ no Compose, spec, produtor, collection, Pagamentos, resultado.

🌿 Só para quem ainda NÃO tem este projeto no GitHub

Já subiu o projeto de Pedidos no Cap 01? Pule este bloco. Só confira no terminal que git remote -v mostra o endereço do GitHub e que git status não tem mudanças que você não reconhece.

Se o seu projeto ainda não está em nenhum repositório remoto, faça agora, antes do Prompt 2:

  1. Em github.com/new, crie um repositório. Nome sugerido: pedidos. Público ou privado, como preferir; se for privado, adicione como colaborador quem vai revisar seu trabalho.
  2. Não marque “Add a README”, “.gitignore” nem “license”. O repositório precisa nascer vazio; se tiver um README, o primeiro push encontra outro histórico e falha.
  3. Na página que aparece, copie o link do repositório (HTTPS, como https://github.com/seu-usuario/pedidos.git, ou SSH).
  4. Copie o prompt abaixo, troque COLE-AQUI-O-LINK pelo seu link e envie ao agente.
Prompt 1B · subir o projeto para o GitHub (só quem ainda não subiu)
[CONTEXTO] Meu projeto de Pedidos ainda não está em nenhum repositório remoto. Criei um repositório vazio no GitHub: COLE-AQUI-O-LINK
[TAREFA] Mostre git status e git remote -v. Se a pasta ainda não for um repositório Git, inicialize com a branch main. Revise ou crie o .gitignore para Java, Maven e IntelliJ (target/, .idea/, *.iml, .env, logs). Mostre a lista exata de arquivos que entrariam no primeiro commit.
[LIMITES] Nunca inclua tokens, senhas pessoais ou arquivos .env. Não use force push e não reescreva histórico. Se o push pedir autenticação, pare e me explique o que fazer: eu mesmo faço o login (gh auth login ou chave SSH).
[ACEITE] Após meu OK na lista: commit inicial em português, remote origin apontando para o link, push da main com upstream. Mostre git log --oneline -3, git remote -v e o endereço da branch main no GitHub.
[PAUSA] Aguarde meu OK antes do commit e antes do push.

Prova: abra o link no navegador e veja seus arquivos na branch main. A senha aula-local do RabbitMQ que entrará no Compose é uma credencial didática, só da sua máquina; senhas e tokens de verdade nunca vão para o repositório.

Parte 1 · 13:45–14:25

Por que uma fila?

Antes de qualquer ferramenta: qual problema a mensageria resolve, quanto ela custa e o que exatamente viaja de um programa para outro.

Parte 1 · primeiro a necessidade 13:45 · 10 min

Esperar na ligação ou deixar uma comanda? [04]

Duas formas de um programa pedir algo a outro

Comunicação síncrona: quem chama espera a resposta daquela chamada antes de continuar. É o HTTP que você já conhece: o Insomnia envia um POST e fica aguardando o status. Se Pedidos chamasse Pagamentos por HTTP e esperasse a aprovação, a resposta à cliente dependeria de Pagamentos estar no ar, rápido, naquele exato momento.

Comunicação assíncrona: quem pede entrega a solicitação a um intermediário e segue sua vida. O resultado chega depois, por outro caminho. Pedidos não conversa diretamente com Pagamentos: deixa uma mensagem num broker (o “trilho de comandas”), e Pagamentos a busca quando puder.

O nome técnico do problema da primeira lanchonete é acoplamento temporal: os dois programas precisam estar vivos ao mesmo tempo para o trabalho acontecer. A fila quebra esse acoplamento: Pedidos pode aceitar solicitações às 14h, Pagamentos pode processá-las às 14h05.

Avance o fluxo abaixo um passo por vez. Antes de cada clique, diga em voz alta o que espera ver na faixa de cima e na de baixo.

Ler o percurso sem animação

    Na nossa API, o HTTP continua tendo resposta. O que muda é seu significado: 202 Accepted é um código HTTP que quer dizer “aceito para processamento, ainda não terminei”. Para saber o resultado, a cliente consulta de novo (GET /pedidos/{id}). Não vamos manter a requisição HTTP aberta esperando o pagamento.

    Código HTTPSignificaQuando aparece hoje
    200 OKDeu certo; aqui está a respostaConsultar o pedido; repetir uma solicitação já concluída
    201 CreatedCriei algo novoCriar pedido
    202 AcceptedAceitei; vou processar depoisSolicitar pagamento
    400 Bad RequestSeu pedido veio mal formatadoJSON inválido, cenário desconhecido
    404 Not FoundNão encontreiPedido que não existe
    409 ConflictConflita com o estado atualPedido já pago; mesma chave com outro conteúdo
    503 Service UnavailableNão consigo atender agoraRabbitMQ fora do ar ao publicar
    504 Gateway TimeoutEsperei outro serviço e desistiSó no desenho síncrono do fluxo acima

    Regra de bolso: códigos 2xx são sucesso, 4xx são problema no que o cliente mandou, 5xx são problema do lado do servidor.

    Antes de revelar: a cliente recebeu 202. O pedido está pago?

    Parte 1 · números, não opinião 13:55 · 10 min

    A conta da dependência [05]

    “Assíncrono é mais robusto” é uma frase fácil de repetir e difícil de justificar. Vamos fazer a conta.

    Disponibilidade se multiplica em cadeia

    Disponibilidade é a fração do tempo em que um serviço responde corretamente. 99,9% parece quase perfeito, mas num mês de 30 dias (43.200 minutos) significa 43 minutos fora do ar.

    Quando um serviço depende de outro numa chamada síncrona, a chance de os dois estarem de pé ao mesmo tempo é o produto das disponibilidades: 99,9% × 99% = 98,9%. O elo mais fraco puxa a cadeia inteira para baixo — e cada serviço novo na cadeia multiplica de novo.

    O mesmo vale para o tempo de resposta (latência): na chamada síncrona, a espera da cliente é a soma das esperas de todos os serviços chamados. Se Pagamentos leva 3 s, a cliente leva pelo menos 3 s.

    Na versão assíncrona, aceitar a solicitação depende de Pedidos e do broker — não de Pagamentos. Mas atenção: o pagamento em si continua dependendo de Pagamentos. A fila não faz Pagamentos funcionar; ela só permite que a solicitação espere.

    Calculadora: mexa em Pagamentos e compare

    Premissas fixas: Pedidos 99,9%; RabbitMQ 99,9%; mês de 30 dias; limite de espera (timeout) de 5 s.

    Só Pagamentos, sozinho, fica fora .

    Síncrono (Pedidos chama e espera)

    Espera da cliente:

    Chance de aceitar a solicitação:

    Cliente sem conseguir pagar:

    Assíncrono (fila no meio)

    Espera da cliente:

    Chance de aceitar a solicitação:

    Cliente sem conseguir solicitar:

    A barra compara minutos por mês em que a solicitação não pode ser aceita. No assíncrono, o resultado ainda espera Pagamentos voltar.

    Outro benefício: a fila absorve picos

    Imagine uma promoção: 500 pedidos de pagamento chegam no mesmo minuto e Pagamentos só processa 100 por minuto. Na chamada síncrona, 400 clientes esperam até o timeout ou recebem erro. Com a fila, as 500 solicitações são aceitas; Pagamentos processa no seu ritmo, e a fila esvazia em cinco minutos. Esse efeito de “amortecedor” se chama nivelamento de carga (load leveling).

    Antes de revelar: qual situação justifica MENOS uma fila?

    Parte 1 · o que viaja 14:05 · 10 min

    O que viaja dentro de uma mensagem [06]

    Falamos em “mensagem” o tempo todo. Mas o que é, fisicamente, a coisa que sai de Pedidos e chega em Pagamentos?

    Envelope e carta

    Uma mensagem é um pacote de dados com duas partes. As propriedades (também chamadas de cabeçalhos) funcionam como o envelope: dizem o formato do conteúdo, se ela deve ser guardada em disco, qual é sua identificação. O corpo (body) é a carta: os dados de negócio, que no nosso caso são um JSON.

    O broker lê o envelope para saber como tratar a mensagem, mas não abre a carta. Para o RabbitMQ, o corpo é só uma sequência de bytes. Quem entende o JSON é o consumidor. Além do envelope, cada publicação informa para qual exchange vai e com qual chave de roteamento — veremos isso na Parte 2.

    Anatomia de uma mensagem: propriedades e corpo MENSAGEM publicada por Pedidos Propriedades · o envelope content_type: application/jsondelivery_mode: 2 (persistente)message_id / headers: identificação e metadados Corpo · a carta (bytes de um JSON) { "versao": 1, "solicitacaoId": "5b1f…", "pedidoId": "89e83d74…", "valor": 37.80, "cenario": "APROVAR" } O broker lê:guardar em disco? que formato? Só o consumidor lê:pedido, valor, cenário na publicação, à parte: exchange+ chave de roteamento
    Envelope e carta. Propriedades orientam o transporte; o corpo carrega o negócio. O RabbitMQ não valida seu JSON — um corpo errado chega intacto e só explode no consumidor.

    Serialização: de objeto Java a bytes, e de volta

    Um objeto Java vive na memória de um programa. Para atravessar a rede, precisa virar bytes: isso é serializar. Do outro lado, os bytes voltam a ser objeto: desserializar. Usaremos JSON porque é texto legível, que você consegue conferir no painel. A biblioteca Jackson, que o Spring já usa no HTTP, faz a conversão.

    1. Pedidos monta um objeto Solicitacao com os dados do pedido.
    2. Jackson transforma o objeto em JSON, e o JSON em bytes.
    3. Os bytes viajam dentro da mensagem até a fila.
    4. Pagamentos recebe os bytes e Jackson reconstrói um objeto Solicitacao — uma cópia, em outro programa.
    Pagamentos · o formato que ele espera receberJava 21 · record
    public record Solicitacao(int versao, String solicitacaoId, String pedidoId,
                              BigDecimal valor, String cenario) { }

    Um record é uma classe Java curta, só com dados: o compilador gera construtor e métodos de leitura. Repare no campo versao. Os dois serviços são implantados separadamente; um dia, Pedidos pode mandar um formato novo enquanto Pagamentos ainda roda o antigo. O número da versão permite ao consumidor reconhecer o formato e recusar o que não entende, em vez de interpretar errado.

    Antes de revelar: Pedidos mandou "valor": "trinta e sete". Quem percebe o erro?

    Parte 1 · intenção da mensagem 14:15 · 10 min

    Comando, evento e consulta [07]

    Nem toda mensagem tem a mesma intenção. Dar o nome certo evita desenhos confusos — e diz quem é dono de cada decisão.

    Comando, evento e consulta comparados COMANDO · “faça” Pedidos Pagamentos ProcessarPagamento • um destinatário que sabe fazer• verbo no imperativo• pode ser recusado• quem envia espera um efeito Nossa solicitação de pagamento EVENTO · “aconteceu” Pagamentos PedidosE-mailAuditoria PagamentoProcessado • fato consumado, no passado• zero, um ou vários interessados• não se recusa um fato• quem publica não sabe quem lê Nosso resultado de pagamento CONSULTA · “me diga” Cliente Pedidos GET /pedidos/{id} • quer uma resposta agora• não muda nada• normalmente síncrona (HTTP)• repetir é inofensivo Como a cliente vê o estado
    Três intenções. Hoje usamos as três: um comando vai para Pagamentos, um evento volta com o resultado, e a cliente consulta o pedido por HTTP.

    Por que a diferença importa

    Um comando tem dono: “Pagamentos, processe isto”. Se o nome for ProcessarPagamento e houver três serviços ouvindo, algo está errado — quem de fato processa? Um evento é o contrário: quem publica PagamentoProcessado não precisa saber quem se interessa. Amanhã um serviço de e-mail pode começar a ouvir sem que Pagamentos mude uma linha. Essa liberdade tem nome: baixo acoplamento.

    No nosso laboratório, o nome das exchanges revela a intenção: pagamentos.comandos leva a solicitação; pagamentos.eventos traz o resultado.

    Classifique: comando, evento ou consulta?

    ReservarEstoque { pedidoId, itens }
    PedidoCriado { pedidoId, total }
    GET /pedidos/89e83d74…
    PagamentoRecusado { solicitacaoId, motivo }
    EnviarEmailConfirmacao { email, pedidoId }
    Qual o total do pedido 4711?
    Parte 2 · 14:25–14:50

    Como o RabbitMQ entrega

    As peças do broker, as regras de roteamento e a vida de uma mensagem dentro da fila — tudo o que você vai ver acontecer no painel daqui a pouco.

    Parte 2 · seis peças, uma função por vez 14:25 · 5 min

    Seis peças, uma função cada [08]

    Um broker de mensagens (intermediário de mensagens) é um programa servidor especializado em receber, guardar e entregar mensagens entre outros programas. Usaremos o RabbitMQ, um broker de código aberto muito usado com Java. Ele fala o protocolo AMQP (Advanced Message Queuing Protocol) — as regras de conversa entre aplicação e broker, como o HTTP é para navegador e servidor.

    O caminho da mensagem: produtor, exchange, binding, fila e consumidor ProdutorPedidos BROKER · RabbitMQ Exchangepagamentos.comandos bindingpagamento.solicitar Filapagamentos.solicitacoes ConsumidorPagamentos remetentecentro de triagemregra na paredecaixa postaldestinatário (analogia do correio — o limite: aqui as “cartas” podem ser copiadas e reentregues)
    O endereço completo. O produtor nunca escreve direto na fila: publica numa exchange com uma chave. O binding é a regra (“chave X vai para a fila Y”); quem decide é a exchange, que compara a chave com todos os seus bindings e copia a mensagem para cada fila que casar — uma, várias ou nenhuma. O consumidor lê da fila.
    PeçaSignificadoNo nosso caso
    ProdutorPrograma que publica uma mensagemPedidos envia a solicitação
    MensagemEnvelope (propriedades) + carta (corpo)ID da solicitação, pedido, valor e cenário
    BrokerPrograma que recebe, guarda e encaminha mensagensRabbitMQ
    ExchangePonto de roteamento dentro do broker; não guarda mensagenspagamentos.comandos
    FilaLugar onde mensagens aguardam entrega, em ordem de chegadapagamentos.solicitacoes
    ConsumidorPrograma que recebe e processaServiço de Pagamentos

    Duas palavras completam o endereço. Uma routing key (chave de roteamento) é um texto enviado junto com a publicação, como pagamento.solicitar. Um binding (ligação) é uma regra configurada no broker: “desta exchange, mande para esta fila as mensagens com tal chave”.

    Conexão e canal: como a aplicação fala com o broker

    A aplicação Java abre uma conexão de rede (TCP, o mesmo tipo de conexão que o HTTP usa por baixo) com o RabbitMQ na porta 5672 e, dentro dela, canais — conversas leves e independentes que compartilham a mesma conexão. Você não vai programar isso: o Spring AMQP cuida. Mas ajuda a ler o painel, que mostra abas Connections e Channels. Se Pagamentos está “ligado” mas não aparece em Connections, ele não está conversando com o broker.

    O painel também fala em virtual host (vhost): um “espaço” separado dentro do mesmo broker, com filas e permissões próprias — como pastas diferentes num mesmo disco. Usaremos só o padrão, chamado /.

    Na volta, os papéis se invertem: Pagamentos vira produtor do resultado (na exchange pagamentos.eventos) e Pedidos vira consumidor (da fila pedidos.resultados). Produtor e consumidor são papéis numa conversa, não tipos de programa.

    Antes de revelar: o serviço de Pagamentos está desligado. Quem guarda a solicitação?

    Parte 2 · regras de roteamento 14:30 · 10 min

    A exchange roteia; não guarda [09]

    Quatro tipos de exchange, uma pergunta: para quais filas copio?

    Quando uma mensagem chega, a exchange compara a chave de roteamento com os bindings de cada fila ligada a ela e copia a mensagem para todas as que casarem. A regra de comparação depende do tipo da exchange:

    • direct — casa quando a chave é exatamente igual à do binding. Um destino claro por tipo de mensagem. É o nosso caso.
    • fanout — ignora a chave e copia para todas as filas ligadas. Útil para avisar vários interessados sobre um fato.
    • topic — a chave é dividida em palavras separadas por ponto, e o binding pode usar curingas: * vale exatamente uma palavra; # vale zero ou mais. pagamento.* casa com pagamento.aprovado, mas não com pagamento.cartao.aprovado.
    • headers — compara propriedades da mensagem em vez da chave. Raro; só para você reconhecer o nome.

    Existe ainda a exchange padrão, sem nome (""), do tipo direct: toda fila fica ligada a ela automaticamente, usando o próprio nome como chave. É por isso que dá para “publicar direto na fila” pelo painel — na verdade, você publica na exchange padrão.

    Ler o percurso sem animação

      Simulador: publique e veja quem recebe

      Tipo da exchange
      Fila A · cobrançabinding:
      Fila B · e-mailbinding:
      Fila C · auditoriabinding:

      Antes de revelar: exchange topic, binding pagamento.*, chave pagamento.cartao.aprovado. A fila recebe?

      Parte 2 · dentro da fila 14:40 · 10 min

      Fila: Ready, Unacked e ack [10]

      Entregar não é o mesmo que terminar

      Uma fila guarda mensagens em ordem de chegada (FIFO, first in, first out: a primeira que entra é a primeira que sai). Cada mensagem passa por estados que o painel mostra como contadores:

      1. Ready (pronta): está na fila, disponível, esperando um consumidor.
      2. Unacked (entregue, sem confirmação): o RabbitMQ entregou a um consumidor, mas continua guardando uma cópia até receber a confirmação.
      3. Ack (acknowledgment, confirmação): o consumidor avisa “terminei”. Só agora a mensagem é apagada.
      4. Se a conexão do consumidor cair antes do ack, a mensagem volta para Ready, marcada como redelivered (reentregue), e irá para o próximo consumidor disponível.

      O prefetch limita quantas mensagens sem ack um consumidor pode segurar ao mesmo tempo. Com prefetch 1, ele recebe uma, confirma, e só então recebe a próxima. Com dois consumidores na mesma fila — os consumidores concorrentes — cada mensagem vai para um deles, nunca para os dois: é assim que se divide trabalho entre instâncias.

      Quando o consumidor não consegue: nack, requeue e fila de erros

      Nem toda entrega termina em ack. O consumidor tem outras respostas possíveis:

      • Nack (negative acknowledgment, “não consegui”) ou reject (“recuso”): as duas dizem ao RabbitMQ que o processamento falhou. A diferença entre elas é técnica e não importa hoje.
      • Junto com o nack vai uma escolha, o requeue (“recolocar na fila”). Com requeue = true, a mensagem volta para Ready e será entregue de novo. Com requeue = false, ela sai da fila.
      • Saiu com requeue = false? Se a fila tiver uma fila de erros configurada, a mensagem vai para lá em vez de sumir. O nome técnico é DLQ (dead letter queue, fila de mensagens “mortas”), o mesmo usado no AWS SQS. No nosso kit são as filas terminadas em .erros.

      Cuidado com requeue = true para um erro que sempre acontece: a mensagem volta, falha, volta, falha… para sempre. Por isso limitamos as tentativas (três, configuradas no consumidor — ponto 16) e depois mandamos para a fila de erros.

      Durável ou temporária?

      Ao criar uma fila, você escolhe se ela é durável (Durable): a fila continua existindo depois que o RabbitMQ reinicia. A alternativa, Transient (temporária), desaparece no reinício. Todas as filas da aula são duráveis. Atenção: durável fala da fila; para as mensagens sobreviverem, elas também precisam ser publicadas como persistentes (ponto 17).

      Ler o percurso sem animação
        Estados de uma mensagem: publicada, Ready, Unacked, removida ou fila de erros Publicada roteada Ready entregue Unacked conexão caiu / nack com requeue ack Removida da fila ✓ rejeitadasem requeue Fila de erros (.erros) sem rota: descartada ou devolvida
        Máquina de estados da mensagem. Toda seta vermelha é um caminho que o seu código precisa prever. A seta de volta para Ready é a origem das mensagens repetidas.
        Indicador do painelLeia assim
        ReadyDisponível para ser entregue a um consumidor
        UnackedEntregue, mas ainda sem confirmação de processamento
        TotalReady + Unacked
        ConsumersQuantidade de consumidores conectados à fila

        Antes de revelar: o painel mostra Ready 0 e Unacked 1 há cinco minutos. O que isso indica?

        Lab A · 14:50–15:15

        Mão no broker, sem código

        Subir o RabbitMQ e brincar com uma fila de treino no painel. Tudo o que vimos na teoria vira contador na tela.

        Lab A · juntos · infraestrutura 14:50 · 10 min

        Uma fila precisa de um lugar para existir [11]

        O RabbitMQ é outro programa. Vamos executá-lo em um container, como já fizemos com o Postgres. A imagem com sufixo management inclui o painel visual de administração. Um healthcheck é uma verificação automática de saúde: indica se o serviço está pronto para responder. Um volume guarda os dados do broker fora do container, para sobreviverem a reinícios.

        PortaProtocoloQuem usa
        5672AMQPAs aplicações Java, para publicar e consumir
        15672HTTPVocê, pelo navegador, para ver o painel
        Prompt 2 · RabbitMQ no Compose
        [CONTEXTO] A API já usa Postgres no Docker. Quero adicionar RabbitMQ à infraestrutura atual.
        [TAREFA] Inspecione o Compose e proponha adicionar RabbitMQ 4.1-management com volume nomeado, healthcheck, usuário local aula e senha didática aula-local. Publique 5672 para as aplicações e 15672 para o painel, somente em 127.0.0.1. Confira conflitos de porta.
        [LIMITES] Preserve o Postgres e seus volumes. Não use down -v, não apague dados e não crie outro Postgres por conveniência. Não altere Java nesta etapa. Credenciais são apenas para ambiente local.
        [ACEITE] Após meu OK, suba somente o necessário. Mostre healthcheck saudável e painel acessível. Explique a diferença entre 5672 (mensagens) e 15672 (painel).
        [PAUSA] Mostre o plano e aguarde meu OK.
        [COMMIT] Só depois do meu OK sobre o diff: commit apenas dos arquivos desta etapa, mensagem em português dizendo o que mudou, e push para origin main. Sem segredos e sem force push. Mostre git log -1 --stat.

        Como abrir o painel do RabbitMQ

        localhost é o nome que todo computador usa para se referir a ele mesmo. O container do RabbitMQ roda na sua máquina e o Compose “publica” a porta 15672 dele na sua máquina; por isso o navegador o encontra em localhost. O 127.0.0.1 no Compose garante que só você acessa: ninguém da rede da sala consegue abrir o seu painel.

        1. Espere o container ficar saudável. O RabbitMQ leva uns 15–20 segundos para ligar; o agente mostra o status healthy no docker compose ps.
        2. No navegador, abra http://localhost:15672 (é http, sem “s”).
        3. Entre com usuário aula e senha aula-local — os mesmos que o Prompt 2 colocou no Compose. São credenciais didáticas, válidas só na sua máquina.
        4. Você cai na aba Overview, com gráficos zerados. Nas abas de cima ficam Connections, Channels, Exchanges e Queues and Streams.
        Aba do painelO que mostraQuando você vai olhar
        OverviewResumo geral: gráficos de mensagens por segundo, totaisPara ver se algo está chegando
        ConnectionsProgramas conectados ao brokerPedidos e Pagamentos estão realmente ligados?
        ChannelsAs conversas dentro de cada conexãoRaramente; diagnóstico avançado
        ExchangesAs exchanges e seus bindings; permite publicar à mãoPontos 12 e 24
        Queues and StreamsAs filas e seus contadores Ready, Unacked, ConsumersA tarde toda
        AdminUsuários, permissões, virtual hostsNão usaremos

        Streams, que aparecem no nome da aba, são outro recurso do RabbitMQ (um registro de mensagens que pode ser relido). Não usaremos hoje.

        O painel não abre ou o login falha

        Página não carrega: o container ainda está subindo ou caiu; peça ao agente docker compose ps e os logs do serviço rabbitmq. Porta ocupada: outro programa usa a 15672; troque a porta do host no Compose, por exemplo 127.0.0.1:15673:15672, e abra localhost:15673. Usuário ou senha recusados: o usuário é criado só na primeira vez que o volume nasce; se o volume já existia com outra senha, use a antiga ou peça ao agente um diagnóstico — não apague o volume sem entender o que há nele.

        Se algo falhar

        Conexão recusada: confirme Docker e container. Porta ocupada: escolha outra porta do host e ajuste a aplicação. Painel aberto, Java sem conexão: confira a porta AMQP e as credenciais. Não apague volumes para tentar novamente.

        Lab A · cada um no seu painel 15:00 · 15 min

        Sua primeira fila, à mão [12]

        Antes de qualquer Java, vamos provocar no painel tudo o que o fluxo animado mostrou: Ready, entrega sem ack, reentrega, consumo e mensagem sem rota. Usaremos uma fila de treino descartável, treino.cafe. Nas filas pagamentos.* e pedidos.*, depois, só observe.

        1. Abra o painel. http://localhost:15672, usuário aula, senha aula-local (veja o ponto 11). Algumas seções das páginas vêm recolhidas: clique no título para abri-las. Os contadores atualizam a cada poucos segundos.
        2. Crie a fila. Aba Queues and Streams → Add a new queue (fica no fim da página). Preencha: Virtual host / (o padrão), Type deixe o padrão, Name treino.cafe, Durability Durable, Arguments em branco → Add queue.
        3. Publique três mensagens. Clique em treino.cafe → seção Publish message. Payload é o corpo da mensagem, a “carta” do ponto 06. Nele, escreva {"texto":"café 1"} e publique. Repita com café 2 e café 3. Preveja antes de olhar: quanto marca Ready?
        4. Espie sem consumir. Seção Get messages. O campo Ack Mode diz o que o painel responde ao RabbitMQ depois de pegar a mensagem: Nack message requeue true (“não processei, devolva”), Automatic ack (“confirmado”, a mensagem some), e duas variantes de Reject. Escolha Nack message requeue true, Messages = 1 → Get Message(s). Você vê a mensagem, e ela volta para a fila. Faça de novo e procure o campo Redelivered.
        5. Consuma de verdade. Mesmo lugar, Ack Mode = Automatic ack → Get Message(s). Preveja: e agora, Ready?
        6. Roteie por uma exchange topic. Aba Exchanges → amq.topic (já vem pronta: as exchanges com prefixo amq. são criadas pelo próprio RabbitMQ) → Add binding from this exchange: para a fila treino.cafe, chave cafe.# → Bind. Publique nessa exchange com chave cafe.expresso. Depois publique com chave cha.verde e leia o aviso que aparece.
        7. Limpe só o que é seu. Na fila treino.cafe, Delete Queue. Nenhuma outra.
        Depois do passoSua previsão👀 O que deve aparecer
        3 · três publicaçõesReady = ?
        verReady 3, Unacked 0, Consumers 0. Ninguém consome, e as mensagens esperam.
        4 · Get com Nack requeueReady = ? Redelivered = ?
        verReady continua 3. Na segunda espiada, Redelivered: ● — é a mesma mensagem, entregue de novo.
        5 · Get com Automatic ackReady = ?
        verReady 2. A mensagem foi confirmada na entrega e sumiu, sem ninguém processá-la. É isto que o ack automático antes do trabalho faz: se o consumidor cair, perde-se a mensagem.
        6 · chave cafe.expressochega à fila?
        verSim: cafe.# casa. Ready 3. A publicação na aba da fila usa a exchange padrão; esta usou amq.topic.
        6 · chave cha.verdechega à fila?
        verNão. O painel avisa Message published, but not routed. A publicação “deu certo” e a mensagem sumiu — o erro silencioso do ponto 09.

        ☕ Intervalo · 15:15–15:30

        Deixe o RabbitMQ ligado. Na volta, o agente constrói os dois serviços.

        Parte 3 · 15:30–16:50

        Do desenho ao código

        O percurso completo, o mapa da bancada e a construção guiada: primeiro o contrato, depois quem publica, depois quem consome, por fim a volta do resultado.

        Parte 3 · o percurso inteiro 15:30 · 5 min

        Siga a mensagem. Observe o estado. [13]

        Agora juntamos tudo: HTTP, banco, exchange, fila, consumidor e o caminho de volta. Avance um passo por vez. Primeiro diga o que espera ver; depois confira estado do pedido, lugar da mensagem e evidência. O desenho é uma simulação didática, sem conexão com seu Docker.

        Ler o percurso sem animação
          Estados do pedido: ABERTO, EM_PAGAMENTO e PAGO ABERTO solicitar pagamento→ 202 EM_PAGAMENTO resultado aprovado PAGO resultado recusado: volta a ABERTO adicionar item ✓ adicionar item → 409resultado atrasado → ignora
          A máquina de estados do pedido. EM_PAGAMENTO existe por causa da fila: é o “entre” a solicitação e o resultado. Enquanto dura, os itens ficam travados — o valor enviado a Pagamentos não pode mudar no caminho.
          Parte 3 · mapa da bancada 15:35 · 5 min

          Dois serviços, cada um com seus dados [14]

          Bancada: aplicações no computador, Postgres e RabbitMQ no Docker SEU COMPUTADOR (host) · IntelliJ com dois terminais InsomniaHTTP → :8080 Navegadorpainel → :15672 API de Pedidosporta 8080 · banco pedidosproduz solicitações Pagamentosporta 8081 · banco pagamentosconsome e decide DOCKER (containers) RabbitMQ:5672 AMQP:15672 painel Postgres:5432 banco pedidos banco pagamentos cada serviço só enxerga o próprio banco · aplicações falam com o Docker por localhost
          Onde cada coisa roda. As aplicações Java rodam no seu computador e acessam o Docker por localhost. Um Postgres hospeda dois bancos para economizar recursos; nenhum serviço toca o banco do outro.

          No desenho, host é o seu computador, do lado de fora dos containers. Docker executa o PostgreSQL e o RabbitMQ. As duas aplicações Java rodam em terminais separados no IntelliJ. Não é preciso colocar tudo em containers para usar mensageria.

          Por que cada serviço tem o próprio banco

          Se Pagamentos pudesse escrever direto na tabela de pedidos, bastaria um UPDATE para pular a fila — e as regras de Pedidos (não reabrir pedido pago, conferir valor) seriam ignoradas. Com bancos separados, a única forma de mudar um pedido é pedir a Pedidos, por HTTP ou mensagem. Cada serviço é dono dos seus dados e das suas regras. Em produção, credenciais e permissões diferentes reforçam essa separação; no kit, a separação é lógica.

          Parte 3 · juntos · contrato 15:40 · 10 min

          Antes do código, combine a mensagem [15]

          O ponto 07 separou comando (“processe este pagamento”) de evento (“este pagamento foi aprovado ou recusado”). Agora escrevemos o formato exato de cada um. O JSON é o contrato que os dois serviços precisam entender — e, como são implantados separadamente, ele precisa ser explícito.

          Campo da solicitaçãoPor que existe
          versao: 1Identifica o formato conhecido pelos dois serviços
          solicitacaoIdIdentifica esta tentativa; deve ser reutilizado no reenvio
          pedidoIdLiga o pagamento ao pedido correto
          valor: 37.80Valor calculado pela API a partir dos itens
          cenario: APROVAR ou RECUSARControla exclusivamente o simulador da aula

          Transação local: o que ela protege e o que não protege

          Uma transação local agrupa mudanças no mesmo banco: ou todas são confirmadas (COMMIT), ou todas são desfeitas. Vamos usá-la para mudar o estado do pedido junto com o registro da solicitação — nunca um sem o outro. Mas o RabbitMQ é outro sistema: a publicação não entra nessa transação. Guarde essa frase; ela volta no ponto 25.

          A resposta de negócio preserva os IDs e o valor e acrescenta aprovado e motivo. O cliente não escolhe o valor enviado ao serviço; a API o lê do pedido.

          Prompt 3 · escreva a spec do fluxo
          [TAREFA] Escreva uma spec curta para pagamentos assíncronos simulados, antes de implementar.
          Declare desde o início as filas pagamentos.solicitacoes.erros e pedidos.resultados.erros. Configure as filas principais para encaminhar falhas a elas, evitando mudar argumentos de filas já existentes durante a aula.
          Contrato HTTP: POST /pedidos/{pedidoId}/pagamentos recebe solicitacaoId UUID e cenario APROVAR ou RECUSAR. O valor vem do pedido, não do cliente. Use POST /pedidos e GET /pedidos/{id} existentes.
          Estados: ABERTO passa a EM_PAGAMENTO; aprovação passa a PAGO; recusa volta a ABERTO. Itens não mudam durante EM_PAGAMENTO. Pedido ausente: 404; estado incompatível ou chave reutilizada com outro conteúdo: 409; formato inválido: 400.
          Mensagens JSON versão 1: solicitação com solicitacaoId, pedidoId, valor, cenario; resultado com os mesmos IDs/valor, aprovado e motivo. Exchange direct pagamentos.comandos + chave pagamento.solicitar → fila pagamentos.solicitacoes. Na volta: pagamentos.eventos + pagamento.resultado → pedidos.resultados.
          [ACEITE] 202 só após confirmação do broker e verificação de roteamento; nunca significa aprovado. Defina repetição persistente, resultado conflitante, reinício e recuperação após falha de publicação.
          [LIMITES] Sem cobrança real, sem banco compartilhado entre serviços, sem afirmar transação única entre Postgres e RabbitMQ. O endpoint síncrono antigo de aprovação deve sair da versão assíncrona.
          [PAUSA] Liste dúvidas, arquivos e testes em etapas pequenas. Aguarde meu OK.
          [COMMIT] Só depois do meu OK sobre o diff: commit apenas dos arquivos desta etapa, mensagem em português dizendo o que mudou, e push para origin main. Sem segredos e sem force push. Mostre git log -1 --stat.

          Parte 3 · ler antes de gerar 15:50 · 10 min

          O que o agente vai escrever: Spring AMQP em três peças [16]

          O agente vai gerar dezenas de linhas. Você só consegue revisar o que reconhece. Spring AMQP é a biblioteca que conecta uma aplicação Spring ao RabbitMQ; ela resume o trabalho em três peças. Leia cada uma pelo efeito, não pela sintaxe.

          1. Declarar a topologia: exchange, fila e binding

          Ao subir, a aplicação pede ao broker que crie (se ainda não existirem) a exchange, a fila e a ligação entre elas. Por isso a fila aparece no painel assim que Pedidos liga.

          RabbitConfig.java · versão simplificada@Configuration
          @Bean
          Declarables topologia() {
              var comandos = new DirectExchange("pagamentos.comandos");          // 1. a exchange
              var solicitacoes = QueueBuilder.durable("pagamentos.solicitacoes") // 2. a fila durável
                      .deadLetterExchange("")                                     //    falhou de vez? vai para...
                      .deadLetterRoutingKey("pagamentos.solicitacoes.erros")       //    ...esta fila de erros
                      .build();
              var ligacao = BindingBuilder.bind(solicitacoes)                     // 3. o binding
                      .to(comandos).with("pagamento.solicitar");
              return new Declarables(comandos, solicitacoes, ligacao);
          }

          2. Publicar: o RabbitTemplate

          O RabbitTemplate é para mensageria o que um cliente HTTP é para requisições: você diz para onde vai e o que vai. O objeto Java é convertido em JSON no caminho.

          Publicação · forma mais simplesPedidos
          rabbitTemplate.convertAndSend(
                  "pagamentos.comandos",   // exchange
                  "pagamento.solicitar",   // chave de roteamento
                  solicitacao);            // objeto Java -> JSON

          A referência da aula vai um passo além: espera a confirmação do broker e verifica se a mensagem não voltou sem rota antes de seguir. Se qualquer das duas falhar, lança exceção — e a API responde 503 em vez de 202.

          PublicadorConfirmado.java · o trecho que importaPedidos e Pagamentos
          var correlacao = new CorrelationData();
          rabbit.send(exchange, chave, mensagem, correlacao);
          var confirmacao = correlacao.getFuture().get(5, TimeUnit.SECONDS); // espera o broker
          if (!confirmacao.isAck() || correlacao.getReturned() != null)      // recusou ou voltou sem rota?
              throw new IllegalStateException("Publicação não confirmada ou sem rota.");

          3. Consumir: o @RabbitListener

          Uma anotação transforma um método comum em consumidor. O Spring abre a conexão, busca mensagens da fila e chama o método para cada uma. Ninguém faz requisição HTTP para Pagamentos.

          SolicitacaoListener.java · Pagamentos@Component
          @RabbitListener(queues = "pagamentos.solicitacoes")
          public void receber(Message mensagem) throws Exception {
              var solicitacao = json.readValue(mensagem.getBody(), Solicitacao.class); // bytes -> objeto
              var resultado = decisoes.registrar(solicitacao);  // COMMIT da decisão
              publicador.enviar("pagamentos.eventos", "pagamento.resultado", resultado);
          }   // terminou sem exceção -> o Spring envia o ack

          E a configuração que liga tudo

          application.yml · trecho de PagamentosYAML
          spring:
            rabbitmq:
              publisher-confirm-type: correlated   # quero o publisher confirm
              publisher-returns: true              # quero saber de mensagem sem rota
              template:
                mandatory: true                    # devolva se não houver rota
              listener:
                simple:
                  acknowledge-mode: auto           # ack depois que o método termina bem
                  prefetch: 1                      # uma mensagem por vez
                  default-requeue-rejected: false  # falhou de vez? não volta para a fila
                  retry:
                    enabled: true
                    max-attempts: 3                # 3 tentativas, depois fila de erros

          @Configuration e @Bean (que você já viu no Cap 01): a classe marcada com @Configuration contém receitas; cada método @Bean devolve um objeto que o Spring cria na subida e entrega a quem precisar. Aqui, a receita devolve a topologia, e o Spring AMQP a envia ao RabbitMQ.

          deadLetterExchange("") configura a DLX (dead letter exchange) da fila: para onde ela manda as mensagens rejeitadas sem requeue. Usamos a exchange padrão "", que entrega pelo nome da fila; por isso a deadLetterRoutingKey é o nome da fila de erros.

          CorrelationData é uma etiqueta que liga cada publicação à confirmação que volta do broker. getFuture().get(5, SECONDS) quer dizer “espere até 5 segundos pela confirmação”; getReturned() diz se a mensagem voltou por falta de rota.

          acknowledge-mode: auto é o ponto que mais confunde. No Spring, “auto” quer dizer: o Spring manda o ack automaticamente depois que seu método termina sem exceção. Não é o “automatic ack” do painel, que confirma na entrega, antes de qualquer trabalho.

          retry + default-requeue-rejected: false juntos evitam o loop infinito: três tentativas dentro do consumidor; esgotou, a mensagem é rejeitada sem voltar para a fila e cai na fila de erros configurada na topologia.

          Antes de revelar: o método do listener lança exceção nas três tentativas. Onde a mensagem termina?

          Parte 3 · juntos · primeira metade 16:00 · 10 min

          Pedidos publica. Ainda não aprova. [17]

          Vamos construir somente a saída de Pedidos. Um publisher confirm é a confirmação enviada pelo broker ao produtor sobre a publicação. Também precisamos detectar mensagem devolvida por falta de rota; confirmar publicação não substitui conferir a fila de destino.

          Prompt 4 · implemente o produtor com uma prova
          [CONTEXTO] Implemente somente a primeira etapa da spec aprovada: Pedidos recebe e publica a solicitação.
          [TAREFA] Adicione Spring AMQP compatível com o Boot do projeto. Declare exchanges, filas duráveis e bindings, incluindo as filas de erro e seus encaminhamentos previstos na spec. Use JSON e mensagem persistente. Registre solicitacaoId e estado no Postgres, atualizando o pedido na mesma transação local. Publique após o registro usando confirmação correlacionada e detecção de retorno sem rota.
          [LIMITES] Não marque o pedido como PAGO ao publicar. Não implemente Pagamentos ainda. Preserve o núcleo sem dependências de RabbitMQ. Remova o atalho HTTP antigo que recebia aprovação do cliente; registre a mudança de contrato.
          [ACEITE] Com consumidor parado: solicitação válida recebe 202 após publicação confirmada; pedido EM_PAGAMENTO; mensagem visível na fila. Falha de publicação responde 503 e preserva a chave para recuperação, sem fingir sucesso. Repetir a mesma chave/conteúdo pendente permite republicar; conteúdo diferente é 409. Não há outbox nem reenvio automático neste laboratório.
          [PAUSA] Mostre testes, diff e o roteiro de prova. Pare antes da próxima etapa.
          [COMMIT] Só depois do meu OK sobre o diff: commit apenas dos arquivos desta etapa, mensagem em português dizendo o que mudou, e push para origin main. Sem segredos e sem force push. Mostre git log -1 --stat.

          Antes de revelar: o RabbitMQ confirmou a publicação. Quem já terminou?

          Parte 3 · requisições que contam uma história 16:10 · 5 min

          Monte sua bancada no Insomnia [18]

          Uma collection reúne requisições salvas. Um ambiente guarda valores reutilizáveis, como a URL da API e o ID do pedido. A collection não executa todo o roteiro sozinha: você envia, observa e atualiza as variáveis indicadas.

          Importe insomnia-pedidos.json pela opção de importação de arquivo. Abra o ambiente base: base_url começa em http://localhost:8080. Envie “01 · Criar pedido”; copie o campo id da resposta para pedido_id.

          RequisiçãoAntes de enviarResultado esperado
          01 · Criar pedido2 cafés, preço 18.90201, ABERTO, total 37.80
          02 · Consultar pedidoCopiar ID retornado para pedido_id200 com o estado atual
          03 · Solicitar aprovaçãoPagamentos parado; ID da solicitação estável202, registro PENDENTE
          04 · Repetir a solicitaçãoMesma chave e mesmo conteúdo202 se pendente; 200 se concluída
          05 · Solicitar recusaNovo pedido em pedido_recusa_id202; depois ABERTO
          06 · Conteúdo conflitanteChave da aprovação, mas cenário RECUSAR409
          07 · Pedido inexistenteUUID que não existe no banco404
          Prompt 5 · adaptar a collection ao meu projeto
          [TAREFA] Confira a collection fornecida e adapte apenas URLs e campos que divergirem do contrato aprovado. Preserve a ordem criar → consultar → solicitar → consultar novamente.
          [ACEITE] Use variáveis de ambiente base_url, pedido_id, pedido_recusa_id e chaves estáveis de solicitação. Explique quais valores eu copio da resposta e onde colo. Inclua aprovação, recusa, repetição, conflito e pedido ausente, com resposta esperada na descrição.
          [LIMITES] Não gere um novo UUID em cada reenvio. Não crie endpoint para fingir aprovação e não substitua a API por mock. Não inclua credenciais pessoais.
          [PAUSA] Mostre o arquivo importável e um roteiro de execução manual.
          [COMMIT] Só depois do meu OK sobre o diff: commit apenas dos arquivos desta etapa, mensagem em português dizendo o que mudou, e push para origin main. Sem segredos e sem force push. Mostre git log -1 --stat.

          Parte 3 · experiência central 16:15 · 10 min

          Desligue o consumidor. A fila continua ali. [19]

          Este é o momento em que o fluxo do ponto 04 vira realidade na sua máquina. Mantenha a API de Pedidos e o RabbitMQ ligados. O serviço de Pagamentos fica parado (ou ainda nem existe). Envie a solicitação, consulte o pedido e abra a fila pagamentos.solicitacoes no painel.

          Não use “Purge” nem “Delete”. Essas ações removem mensagens ou a fila. Para apenas observar, os contadores bastam. Também não use “Get messages” durante a prova: no ponto 12 você viu que isso entrega a mensagem a você, e pode consumir ou reenfileirar e alterar sua observação.

          Antes de revelar: agora desligamos o próprio RabbitMQ. A API consegue garantir 202?

          Parte 3 · juntos · segunda aplicação 16:25 · 15 min

          Pagamentos recebe uma tarefa [20]

          Um listener é o método configurado para receber as entregas da fila — o @RabbitListener do ponto 16. O Spring AMQP conecta esse método ao RabbitMQ. Ele não precisa de uma requisição HTTP para cada pagamento.

          O simulador é determinístico: a mesma entrada sempre dá o mesmo resultado, sem sorteio — APROVAR aprova, RECUSAR recusa. Pagamentos terá sua própria memória durável no Postgres: uma decisão por solicitacaoId, protegida por uma restrição única no banco (a tabela recusa duas linhas com a mesma chave). Se a mesma solicitação chegar outra vez, lê a decisão já salva e devolve o mesmo resultado.

          Prompt 6 · serviço separado de pagamentos
          [CONTEXTO] A API publica solicitações versão 1 na fila pagamentos.solicitacoes.
          [TAREFA] Crie a aplicação pagamentos na pasta pagamentos/, dentro deste mesmo repositório, como projeto Maven independente (não é módulo do pom de Pedidos), Java 21 e a mesma versão Boot de Pedidos, com seu Maven Wrapper. Use Spring AMQP e seu próprio banco pagamentos. Não leia tabelas de Pedidos. Configure porta 8081 para não conflitar.
          Receba JSON validando versão, IDs, valor positivo e cenário. Simule APROVAR ou RECUSAR de forma determinística. Persista uma única decisão por solicitacaoId, com restrição única. Mesma chave com outro pedido, valor ou cenário deve falhar.
          Após salvar a decisão, publique resultado na exchange pagamentos.eventos com chave pagamento.resultado. Preserve IDs e valor. Se receber novamente, reutilize a decisão salva e republique o mesmo resultado. Espere confirmação e detecte falta de rota.
          [LIMITES] Sem SDK de banco financeiro, aleatoriedade, cartão ou cobrança real. Não confirme processamento antes de salvar e publicar. Configure tentativas limitadas e filas de erro para não criar loop infinito; documente sem aprofundar a operação de recuperação.
          [ACEITE] Ao ligar o serviço, a mensagem pendente sai de Ready; o resultado aparece em pedidos.resultados se seu consumidor estiver parado. Um registro de pagamento sobrevive ao reinício.
          [PAUSA] Mostre o plano antes de criar; após meu OK, execute, teste e mostre as provas.
          [COMMIT] Só depois do meu OK sobre o diff: commit apenas dos arquivos desta etapa, mensagem em português dizendo o que mudou, e push para origin main. Sem segredos e sem force push. Mostre git log -1 --stat.

          Parte 3 · juntos · fechar o circuito 16:40 · 10 min

          O resultado precisa voltar [21]

          Uma fila esvaziar não atualiza o banco de Pedidos por mágica. Agora Pedidos se torna consumidor do resultado. Ele confere a identificação, aplica a transição da máquina de estados do ponto 13 e registra que aquela solicitação terminou.

          Prompt 7 · aplicar o resultado em Pedidos
          [TAREFA] Implemente em Pedidos o consumidor da fila pedidos.resultados.
          Valide versão 1, solicitacaoId existente, mesmo pedido e valor. Neste laboratório determinístico, resultado também deve corresponder ao cenário pedido. Resultado repetido já concluído não altera novamente o pedido. Resultado conflitante deve falhar com evidência e ir para tratamento de erro limitado.
          Na mesma transação local, conclua a solicitação e aplique: aprovado → PAGO; recusado → ABERTO. Preserve ID, itens e total. Confirme processamento somente após concluir essa transação. Não reabra pedido pago por evento atrasado.
          [ACEITE] Prove aprovação, recusa, resultado duplicado após reiniciar e conteúdo incompatível. Consultar o pedido deve revelar o estado final; logs sozinhos não bastam.
          [LIMITES] Sem endpoint que aceite aprovado do cliente. Sem transação global entre bancos e RabbitMQ. Não capture exceções para fingir que tudo funcionou.
          [PAUSA] Mostre diff, testes e evidências do fluxo completo.
          [COMMIT] Só depois do meu OK sobre o diff: commit apenas dos arquivos desta etapa, mensagem em português dizendo o que mudou, e push para origin main. Sem segredos e sem force push. Mostre git log -1 --stat.

          Parte 4 · 16:50–17:15

          Quando algo dá errado

          Mensagens se repetem, processos caem no meio, publicações ficam incertas. A pergunta profissional não é “vai falhar?”, é “quando falhar, o que acontece com o pedido?”.

          Parte 4 · a promessa do transporte 16:50 · 5 min

          Quantas vezes uma mensagem chega? [22]

          Três promessas de entrega

          Todo sistema de mensagens escolhe, por configuração, uma destas garantias de entrega:

          • No máximo uma vez (at-most-once): o consumidor confirma ao receber, antes de trabalhar. Se cair no meio, a mensagem já foi apagada: pode perder, nunca repete. Foi o “Automatic ack” do painel.
          • Pelo menos uma vez (at-least-once): o consumidor confirma depois de trabalhar. Se cair no meio, a mensagem volta: nunca perde (com fila durável, mensagem persistente e publisher confirm), mas pode repetir. É a nossa escolha.
          • Exatamente uma vez (exactly-once): a promessa que todo mundo quer. Na entrega entre sistemas independentes, ela não existe de graça — sempre há um instante em que “já fiz, mas ainda não avisei”.

          A saída prática é combinar: entrega pelo menos uma vez + consumidor idempotente = efeito exatamente uma vez. A mensagem pode chegar duas vezes; o pagamento só acontece uma.

          Garantias de entrega comparadas quando o consumidor confirmase cair no meiocusto No máximo uma vezat-most-onceack ao recebermensagem perdidaperde dado Pelo menos uma vez ✓at-least-once · nossa escolhaack depois do trabalhomensagem repetidaexige idempotência “Exatamente uma vez”efeito, não entregapelo menos uma vez+ consumidor idempotenteprojeto, não configuração
          Troque perda por repetição. Perder um pagamento é irrecuperável; uma repetição pode ser reconhecida e ignorada. Por isso escolhemos repetir — e aprendemos a reconhecer.

          Antes de revelar: um consumidor com ack automático na entrega processa notificações de “pedido saiu para entrega”. Aceitável?

          Parte 4 · pare e diferencie 16:55 · 5 min

          Três confirmações que não significam a mesma coisa [23]

          SinalQuem envia → quem recebeO que prova
          ① HTTP 202API → cliente do InsomniaSolicitação aceita conforme o contrato
          ② Publisher confirmRabbitMQ → produtorBroker assumiu a publicação; não é resultado do negócio
          ③ Consumer ackConsumidor → RabbitMQEssa entrega foi processada segundo o consumidor

          O diagrama abaixo é um diagrama de sequência: cada coluna é um participante, o tempo corre de cima para baixo e cada seta é uma mensagem. Os números nas setas são as três confirmações da tabela.

          Ler o percurso sem animação

            Na configuração AUTO do Spring AMQP, o framework confirma após o listener terminar com sucesso. Isso é diferente de mandar o broker considerar a entrega concluída antes do processamento — o “Automatic ack” que você testou no painel.

            Antes de revelar: o banco salvou o resultado, mas o processo caiu antes do ack. O que pode acontecer?

            Parte 4 · prática de idempotência 17:00 · 10 min

            A mesma mensagem pode chegar duas vezes [24]

            Idempotência: repetir sem mudar o resultado

            Uma operação é idempotente quando executá-la uma ou várias vezes produz o mesmo efeito. Compare:

            Idempotente

            status = PAGO — repetir deixa PAGO.

            PUT /pedidos/4711 com o mesmo corpo.

            “Grave a decisão de S1, se ainda não existir.”

            Não idempotente

            saldo = saldo - 37.80 — repetir desconta de novo.

            POST /pedidos — cada chamada cria outro pedido.

            “Cobre R$ 37,80.”

            Como pagamento é, por natureza, não idempotente, nós o tornamos idempotente com uma chave de idempotência: a solicitacaoId. Antes de agir, o consumidor pergunta ao próprio banco: “já processei esta chave?”. Se sim, devolve o que já decidiu. A restrição única na tabela garante isso mesmo que duas entregas cheguem ao mesmo tempo: só uma linha entra.

            E quando a chave é a mesma mas o conteúdo mudou (outro valor, outro cenário)? Isso não é repetição, é conflito — alguém reutilizou a chave de forma errada. O consumidor rejeita, sem alterar a decisão anterior.

            Ler o percurso sem animação

              Simulação rápida · S1 chega três vezes

              Escolha uma estratégia e observe o número de efeitos.

              Prompt 8 · prove a repetição, não apenas a prometa
              [TAREFA] Proponha e execute uma prova de duplicidade usando somente os serviços e dados deste laboratório.
              Com Pagamentos parado, publique duas vezes a mesma solicitacaoId com o mesmo conteúdo. Ligue o consumidor e confira uma decisão persistida e o pedido correto. Reinicie Pagamentos e Pedidos; republique o mesmo JSON diretamente no painel didático para testar o consumidor, não só a proteção do HTTP.
              [ACEITE] Conte registros por chave, consulte o pedido e mostre que reiniciar não apaga a proteção contra duplicidade. Teste também a mesma chave com valor incompatível: deve ser rejeitada, sem mudar a decisão anterior.
              [LIMITES] Não purgue filas nem apague volumes. Faça as publicações manuais apenas no ambiente local de aula. Explique por que enviar com UUID novo seria outra solicitação.
              [PAUSA] Mostre o roteiro antes de executar ações; depois apresente resultados observados.
              [COMMIT] Se esta etapa criou ou alterou arquivos (testes, roteiro de prova), só depois do meu OK: commit desses arquivos em português e push para origin main. Sem segredos e sem force push.

              👀 Como conferir sem depender de uma mensagem de log

              No banco de Pagamentos, conte registros com a mesma chave: deve existir um. No banco de Pedidos, a solicitação mantém um resultado. Consulte o pedido para conferir estado e total. Para republicar após reiniciar, use a exchange e a chave corretas e o JSON original, como no guia de execução.

              Como republicar uma mensagem pelo painel (usado no Prompt 8)

              Repetir o POST de uma solicitação já concluída não chega ao consumidor: Pedidos responde 200 com o resultado guardado e nem publica. Para testar a proteção do consumidor, publique a mensagem à mão:

              1. Aba Exchanges → pagamentos.comandos → seção Publish message.
              2. Routing key: pagamento.solicitar.
              3. Properties: content_type = application/json e delivery_mode = 2 (persistente).
              4. Payload: o JSON original da solicitação, com os IDs reais do seu pedido e da sua solicitação:
              Payload · troque os IDs pelos seusJSON
              {"versao":1,"solicitacaoId":"ID-DA-SOLICITACAO","pedidoId":"ID-DO-PEDIDO","valor":37.80,"cenario":"APROVAR"}

              Depois de publicar, confira: continua existindo um único registro para aquela chave em Pagamentos, e o pedido não mudou. O passo a passo completo, inclusive para republicar um resultado, está no guia de execução.

              Parte 4 · diagnóstico com evidências 17:10 · 5 min

              Quando o fluxo para, encontre a fronteira [25]

              O problema da escrita dupla

              Pedidos precisa fazer duas coisas: gravar no Postgres e publicar no RabbitMQ. São dois sistemas, sem uma transação que abranja os dois. Isso se chama escrita dupla (dual write), e sempre deixa uma janela: se o processo cair entre uma e outra, o mundo fica pela metade. É a janela A do ponto 23.

              Nossa referência escolhe a ordem “grava, depois publica” e deixa o registro PENDENTE como pista: a cliente recebe 503 e pode reenviar a mesma chave, que republica sem duplicar. Não há republicação automática. Um padrão chamado outbox resolve isso gravando a mensagem numa tabela, na mesma transação, e publicando depois por um processo separado — fica para a continuação de mensageria. Não vamos afirmar que já o implementamos.

              ObservaçãoHipótese a conferirPróxima evidência
              Ready cresce, Consumers = 0Consumidor desligado ou não conectadoProcesso Pagamentos, fila e credenciais
              Unacked por muito tempoListener ainda trabalhando ou travadoLogs, banco e duração do processamento
              Pedido EM_PAGAMENTO; fila de solicitações vaziaResultado ainda pendente ou em erroFila pedidos.resultados e filas de erro
              HTTP 503 ao solicitarNão foi possível confirmar a publicaçãoSaúde do broker; reenvio com a mesma chave
              Mensagem em *.errosContrato inválido ou falha persistenteExceção e conteúdo, antes de reenviar

              Uma fila de erro (em inglês, dead letter queue, DLQ) guarda mensagens que não concluíram após as tentativas configuradas. Neste kit, são pagamentos.solicitacoes.erros e pedidos.resultados.erros. É uma contenção básica: a mensagem para de travar a fila e fica disponível para análise. Reprocessamento e políticas mais completas ficam para a continuação.

              Prompt 9 · investigar sem apagar a evidência
              [CONTEXTO] Tenho um pedido EM_PAGAMENTO que ainda não terminou.
              [TAREFA] Investigue antes de alterar. Siga solicitacaoId pelos registros e pelas filas. Confira Ready, Unacked, Consumers, mensagens de erro e conexão dos dois serviços. Separe observações de hipóteses.
              [ACEITE] Localize a última fronteira comprovada e proponha a menor recuperação. Se publicação ficou incerta, reutilize a mesma chave e conteúdo. Se o contrato está inválido, explique a causa antes de reprocessar.
              [LIMITES] Não apague volumes, não purgue filas, não marque manualmente o pedido como PAGO e não reenvie em loop infinito.
              [PAUSA] Mostre diagnóstico e plano; aguarde meu OK.
              [COMMIT] Se aprovarmos uma correção de código, commit só dela, em português, e push para origin main. Diagnóstico sem mudança não gera commit.

              Antes de revelar: um pedido está EM_PAGAMENTO há uma hora e a fila de solicitações está vazia. Qual a primeira ação?

              Fechamento · 17:15–17:30

              Conduza e explique

              Um experimento curto, sozinho, e cinco perguntas para responder sem abrir o código.

              Fechamento · sozinhos · 10 min 17:15

              Agora conduza uma recusa e uma nova tentativa [26]

              Crie um pedido novo. Solicite RECUSAR e prove que ele volta a ABERTO sem alterar itens ou total. Depois solicite APROVAR com outra solicitacaoId e prove que termina PAGO. Reenvie o resultado antigo da recusa: ele não deve reabrir o pedido.

              Em sala, faça pelo menos a previsão escrita e a primeira metade (recusa). O restante segue para casa.

              Ajuda de prompt
              Prompt 10 · revisão do meu experimento
              [CONTEXTO] Fiz recusa e depois aprovação para o mesmo pedido, com duas solicitacaoId distintas.
              [TAREFA] Revise minhas evidências e o diff sem editar arquivos. Confira correlação, estado final, preservação do total e tratamento do resultado antigo repetido.
              [ACEITE] Aponte problemas concretos com reprodução, ou justifique por que o comportamento está correto. Não invente um erro para preencher a revisão.
              [LIMITES] Não adicionar framework ou serviço. Compare com a spec aprovada.
              [PAUSA] Entregue um parecer curto: evidência, risco e correção mínima quando necessária.

              👀 Resultado de referência

              Tentativa A recusada → pedido ABERTO. Tentativa B aprovada → pedido PAGO. A mensagem repetida de A encontra seu próprio registro já concluído e não aplica nova transição. Trocar o ID da solicitação dentro de um resultado não é repetição válida.

              Fechamento · cinco perguntas 17:25 · 5 min

              Explique o sistema sem decorar anotações [27]

              Antes de revelar: a fila ficou vazia. Qual evidência fecha a história?

              1. Quem guarda o trabalho quando Pagamentos está desligado — e quem não guarda?
              2. Qual a diferença entre 202, publisher confirm e consumer ack?
              3. Por que escolhemos “pelo menos uma vez”, e o que isso exige do consumidor?
              4. Por que usar a mesma chave ao reenviar, e o que uma chave nova significa?
              5. O que ainda não resolvemos entre banco e publicação?

              🏠 Em casa

              Consolide o que você consegue demonstrar

              Em casa · consolidar

              Traga uma prova e uma dúvida [28]

              Reproduza o fluxo a partir de uma pasta limpa usando o guia de execução. Registre aprovação, recusa e duplicidade após reinício. Termine o desafio do ponto 26. Escolha uma falha, peça um diagnóstico ao agente (prompt 9) e confira se ele preservou a evidência.

              Entregue o link do seu repositório (branch main, com um commit por etapa), sua spec, os testes e uma explicação curta: “Meu pagamento estava pendente porque… A evidência foi… A recuperação foi…” Não basta uma captura de “BUILD SUCCESS”.

              Para rever a teoria, percorra de novo os seis fluxos animados e tente narrar cada passo antes de clicar em avançar. Se consegue explicar a janela A e a janela B do ponto 23 sem olhar, você entendeu o essencial do dia.

              O aprofundamento de OO/SOLID começa a partir do Cap 03. Mensageria volta adiante com recuperação, outbox, políticas de erro e outras decisões. O capítulo anterior de OO/SOLID foi preservado como material de referência para reorganização.

              Consulta rápida

              Glossário de bolso [29]

              Os termos do dia, na ordem em que aparecem.

              Síncrono
              Quem chama espera a resposta daquela chamada antes de continuar.
              Assíncrono
              Quem pede entrega a solicitação a um intermediário e recebe o resultado depois, por outro caminho.
              Acoplamento temporal
              Dois programas precisam estar vivos ao mesmo tempo para o trabalho acontecer.
              202 Accepted
              Código HTTP: “aceito para processamento, ainda não terminei”.
              Disponibilidade
              Fração do tempo em que um serviço responde corretamente. Em cadeia síncrona, as disponibilidades se multiplicam.
              Consistência eventual
              Os dados ficam corretos “um pouco depois”, não no mesmo instante da solicitação.
              Mensagem
              Propriedades (envelope) + corpo (carta, aqui um JSON).
              Payload
              O corpo da mensagem, como o painel chama.
              Serializar
              Transformar um objeto em bytes para viajar; desserializar é o caminho de volta.
              Comando
              Mensagem que pede uma ação a um destinatário que sabe executá-la. Pode ser recusado.
              Evento
              Mensagem que informa um fato já ocorrido, para quem se interessar.
              Broker
              Programa que recebe, guarda e entrega mensagens. Aqui, o RabbitMQ.
              AMQP
              Protocolo de mensagens usado entre aplicações e o RabbitMQ (porta 5672).
              Virtual host (vhost)
              Espaço separado dentro do broker, com filas e permissões próprias. Usamos o padrão, /.
              localhost / host
              O seu próprio computador, visto por ele mesmo; “host” é o lado de fora dos containers.
              Exchange
              Ponto de roteamento do broker. Copia mensagens para filas; não guarda.
              Routing key
              Chave de roteamento enviada com a publicação.
              Binding
              Regra que liga uma exchange a uma fila, com uma chave ou padrão.
              Direct / fanout / topic
              Tipos de exchange: chave igual / todas as filas / padrões com * e #.
              Fila
              Onde as mensagens aguardam entrega, em ordem de chegada.
              Ready / Unacked
              Disponível para entrega / entregue mas ainda sem confirmação.
              Ack
              Confirmação do consumidor: “terminei, pode apagar”.
              Nack / reject
              Resposta do consumidor: “não consegui processar”.
              Requeue
              Recolocar na fila. Com requeue a mensagem volta para Ready; sem ele, sai da fila (e vai para a DLQ, se houver).
              Prefetch
              Quantas mensagens sem ack um consumidor pode segurar de uma vez.
              Consumidores concorrentes
              Várias instâncias na mesma fila; cada mensagem vai para uma só.
              Redelivered
              Marca de mensagem que está sendo entregue de novo.
              Durável / persistente
              Fila que sobrevive ao reinício do broker (o oposto é Transient) / mensagem gravada em disco.
              Publisher confirm
              Confirmação do broker ao produtor: “assumi esta publicação”.
              Mandatory
              Opção de publicação: se não houver rota, devolva a mensagem ao produtor.
              At-least-once
              Garantia “pelo menos uma vez”: nunca perde, pode repetir.
              Idempotência
              Repetir a operação preserva o mesmo efeito.
              Chave de idempotência
              Identificador estável de uma tentativa (a solicitacaoId).
              DLX
              Dead letter exchange: para onde uma fila manda as mensagens rejeitadas sem requeue.
              Fila de erros (DLQ)
              Destino de mensagens que falharam além do limite de tentativas.
              Escrita dupla
              Gravar no banco e publicar no broker sem uma transação comum; deixa uma janela de falha.
              Outbox
              Padrão que grava a mensagem numa tabela na mesma transação e publica depois. Não implementado hoje.