BACKEND MODERNO · CAPÍTULO 03

O pagamento
vira história.

A fila entrega e esquece.
O Kafka anota e deixa reler.

Kafka do zero, sem pressa: por que um registro que não se apaga muda o jogo, o que são tópico, partição, offset e grupo — e uma cafeteria de verdade, com React, Spring e Python, ganhando um Kafka no meio.

PedidoPago · v1Pedido 4711 · R$ 37,80offset 0Ler não apaga
📖 Antes🏫 Em sala🏠 Em casa🔤 Glossário

📖 Antes da aula

Leia para reconhecer o problema. Nada aqui exige que você já conheça Kafka.

Antes da aula · ponto de partida

O pedido foi pago. Quem mais quer saber? [01]

No Cap 02, Pedidos e Pagamentos passaram a conversar por mensagens no RabbitMQ. Pedidos deixa uma solicitação na fila, Pagamentos decide quando puder, e o resultado volta por outra fila. No fim, o pedido fica PAGO.

O que já existe e quem quer saber do pagamento Pedidos8080 · Postgrespedido PAGO RabbitMQsolicitaçõesresultados Pagamentos8081 · decide comandoresultado Novos interessados:Financeiro quer um painel de vendas · Auditoria quer o histórico · Marketing quer saber “quem comprou” e agora?
O que você já tem. O circuito de pagamento funciona. Hoje surge uma pergunta diferente: como avisar várias áreas de que algo aconteceu, sem que Pedidos precise conhecer cada uma delas?

Hoje usamos o Apache Kafka. Ele também transporta mensagens, mas funciona como um caderno de registros: o que foi escrito fica escrito, e quem chega depois ainda pode ler desde o começo. Essa diferença vai ficar visível nos simuladores antes de qualquer código.

Os pagamentos continuam simulados. APROVAR e RECUSAR são cenários de aula; não há dinheiro real envolvido.

Antes da aula · organização

O plano da tarde [02]

Quatro partes. Primeiro a ideia (por que um caderno e não uma fila), com três simuladores. Depois montamos com o agente a Loja Café sem Kafka: front, API e um Pagamentos chamado por HTTP, até o problema aparecer na tela. Então o Kafka entra no meio, e um serviço que chega depois lê a história inteira. No fim, comparamos RabbitMQ com Kafka.

InícioTempoEtapaPontos
13:3010 minAbertura: o plano de hoje01–03
13:4070 minParte 1 · A ideia: três vídeos, três simuladores, as seis peças04–08
14:5010 minParte 2 · A Loja Café e a bancada09
15:0030 minAto 1 · API em Spring e vitrine em React10–11
15:3025 minAto 2 · Pagamentos por HTTP e o teste do Flask desligado12–13
15:5515 min☕ Intervalo—
16:1040 minParte 3 · Ato 3 · Kafka no meio, o mesmo teste de novo14–16
16:5015 minAto 4 · O Analytics chega depois e lê o passado17
17:055 minAto 5 · Dois caixas no mesmo grupo18
17:1010 minParte 4 · RabbitMQ ou Kafka: lutador e defender ou refutar20
17:2010 minFechamento e tarefa de casa21–22

📓 Abrir caderno livre · Baixar collection do Insomnia (Loja Café) · Guia de execução e recuperação

🏫 Em sala

Entenda, preveja, execute e confira

Abertura · juntos 13:30 · 10 min

O plano de hoje: sair do zero no Kafka [03]

O objetivo é que você saia sabendo o que é o Kafka, por que ele existe ao lado do RabbitMQ e onde ele faz diferença num sistema de verdade. Por isso a tarde tem duas metades: primeiro a ideia, sem ferramenta; depois um sistema que você monta com o agente, sem Kafka e com Kafka.

  1. Ideia (vídeos + simuladores): fila × log, offset, grupos, partições e chave.
  2. Sem Kafka: a Loja Café com React, Spring e um Pagamentos em Python chamado por HTTP. Funciona, até o Pagamentos cair.
  3. Com Kafka: o mesmo sistema, a mesma tela, as peças conversando por eventos. Um leitor que chega depois lê a história inteira.
Parte 1 · 13:40–14:50

A ideia: um caderno, não uma fila

Antes da ferramenta, o modelo mental. Se você entender estes cinco pontos, o resto do dia é aplicar.

Parte 1 · primeiro o problema 13:40 · 20 min

A fila entrega e esquece [04]

🎮 Duas cozinhas, versão 8-bit (1 min 11 s, com som). Aperte play quando a turma estiver pronta. A caixa de diálogo funciona como legenda.
Transcrição do vídeo
  1. Tico: Duas cozinhas, um mesmo café. Aperte start!
  2. Tico: Fase um: a cozinha da comanda.
  3. Beto: Comanda no trilho! Um café... pronto! Pro lixo!
  4. Beto: Pão de queijo, pronto! Bolo, pronto! Lixo, lixo, lixo!
  5. Lia: Beto, fechando o caixa: quantos cafés a gente vendeu hoje?
  6. Beto: Ih, Lia... as comandas foram todas pro lixo.
  7. Tico: A fila entrega e esquece. Ótima pra fazer o prato. Péssima pra contar a história.
  8. Tico: Fase dois: a cozinha do caderno.
  9. Beto: Linha zero: café. Linha um: pão de queijo. Linha dois: bolo.
  10. Beto: Li tudo. Meu marcador tá na linha três. E ninguém arranca página!
  11. Lia: Agora eu! Começo na linha zero... três pedidos. Tá tudo aqui!
  12. Tico: E eu? Sou o contador. Só cheguei no fim do mês!
  13. Lia: Pode ler desde o começo, Tico. O caderno não esquece.
  14. Tico: Comanda é fila: ler consome. Caderno é log: ler só avança o seu marcador.
  15. Tico: No Kafka, quem chega depois ainda lê desde o começo. Fase completa!

O que uma fila faz com a mensagem

No RabbitMQ, a mensagem vive na fila até alguém confirmar que a processou (o ack do Cap 02). Confirmou, ela sai. Isso é ótimo para distribuir trabalho: cada solicitação de pagamento deve ser processada uma vez e pronto.

Mas há outro tipo de necessidade: contar o que aconteceu para quem quiser saber, inclusive para quem ainda não existe. Para isso, apagar depois de entregar é um problema. Um serviço novo que liga hoje não tem como ver os pagamentos de ontem.

No RabbitMQ, dá para criar uma fila para cada interessado (o fanout do Cap 02). Mas cada fila só recebe o que chegar depois de ela existir. E se o painel de vendas tiver um bug e precisar recalcular tudo? As mensagens já foram embora.

Fila: a mensagem sai ao ser lida. Caderno: a mensagem fica e cada leitor tem seu marcador. FILA (RabbitMQ) P3 P4 leitor P1 e P2: lidas e apagadasquem chegar depois não vê CADERNO (Kafka) 0·P1 1·P2 2·P3 3·P4 ← novos registros entram no fim painel: vai no 2 auditoria: no 0 ler não apaga;cada leitor guarda até onde leu
A diferença de hoje. Na fila, ler consome. No caderno, ler só avança o seu marcador. O registro continua lá para os outros.

🧪 Simulador · fila × caderno

Publique três pagamentos, deixe o Financeiro ler os três e só então chame o Analytics, que “chegou depois”. Antes do último clique, diga em voz alta o que ele vai encontrar em cada lado.

Fila (RabbitMQ)

ler = retirar

Caderno (Kafka)

ler = avançar o próprio marcador

Comece publicando um pagamento.

Antes de revelar: qual necessidade a fila do Cap 02 atende MAL?

Parte 1 · o mecanismo 14:00 · 15 min

Um caderno que só cresce: log e offset [05]

Log: escrever só no fim

Em computação, log é uma sequência de registros em que só se acrescenta no fim. Ninguém edita a linha 3 nem apaga a linha 5. O nome técnico disso é append-only (“só acrescentar”). O Kafka é, no fundo, um log muito rápido, guardado em disco e acessado pela rede.

Cada registro recebe um número de posição: o offset. O primeiro é 0, o próximo 1, e assim por diante. Offset é como o número da linha no caderno. Ele nunca muda e nunca se repete naquela sequência.

Cada registro tem três partes que importam hoje: a chave (opcional, um texto como o ID do pedido), o valor (o conteúdo, para nós um JSON) e o horário em que foi gravado.

Tópico: um caderno com nome

Um tópico é um log com nome, dedicado a um assunto. Hoje teremos o tópico pedidos-pagos. Quem escreve é o produtor (Pedidos). Quem lê é o consumidor (Analytics, Auditoria). O programa que guarda os tópicos é o broker, o servidor do Kafka.

Repare na palavra que não apareceu: fila. Não há exchange, binding ou routing key. O produtor escolhe o tópico; o consumidor escolhe o tópico. Roteamento rico, como o do RabbitMQ, não é o forte do Kafka.

Anatomia de um registro no tópico pedidos-pagos tópico pedidos-pagos offset 0pedido 4711 offset 1pedido 5120 offset 2pedido 4800 próximo… Registro (offset 1) chave"5120…" valor{"versao":1, "valor":37.80…} horário2026-10-10 15:02:11 o broker não abre o valor: são bytes
Um registro. Offset é a posição. Chave e valor são do produtor. Como no RabbitMQ, o broker não interpreta o JSON; quem valida é o consumidor.

Antes de revelar: o Analytics leu o registro de offset 1. O que acontece com ele no tópico?

Parte 1 · quem lê, e até onde 14:15 · 15 min

Cada grupo, seu marcador [06]

Grupo de consumidores

Quem lê um tópico se identifica com um nome de grupo (consumer group). Para o Kafka, “Analytics” e “Auditoria” são grupos diferentes, mesmo que usem o mesmo programa. Cada grupo tem o seu marcador: o offset até onde já leu.

Quando o consumidor termina de processar registros, ele avisa o broker: “o grupo analytics já leu até aqui”. Isso se chama commit do offset. O marcador fica guardado no Kafka, não no programa. Por isso, se o programa reiniciar, ele continua de onde parou.

A distância entre o fim do tópico e o marcador do grupo é o lag (atraso): quantos registros aquele grupo ainda não leu. Lag crescendo é o equivalente ao “Ready crescendo” do Cap 02.

Rebobinar: o replay

Como o registro não some, um grupo pode voltar o marcador para o início e ler tudo de novo. Isso é replay (reprocessamento). Serve para recalcular um painel depois de um bug, ou para alimentar um serviço novo com o passado. Um grupo que nunca leu nada pode começar do início ou do fim; quem decide é a configuração do consumidor (auto-offset-reset).

🎮 Mundo 2 · O save game (1 min, com som). O Beto tira o fio da tomada: o painel zera, o marcador não. Depois, pratique no simulador abaixo.
Transcrição do vídeo
  1. Tico: Mundo dois: a sala da Programa AI. Fase um: o save game.
  2. Lia: Beto, o painel do Analytics já mostra quatro pedidos pagos. Lindo!
  3. Beto: Vou só trocar o carregador... Ops! Tirei o fio da tomada!
  4. Beto: Religuei. Zero pedidos?! Sumiu tudo!
  5. Lia: Calma. Será que o Kafka apagou os pedidos?
  6. Tico: Não. O tópico continua com os quatro registros. Ler não apaga.
  7. Tico: O que zerou foi a memória do programa. O marcador do grupo ficou salvo no Kafka, lá no fim.
  8. Beto: Então, pro Kafka, está tudo lido... e o meu painel ficou vazio.
  9. Tico: É como no videogame: o progresso fica no save, não na tela. Vamos rebobinar o marcador.
  10. Lia: Um, dois, três, quatro! O painel voltou inteirinho.
  11. Tico: O painel é só uma visão. A fonte da verdade é o log.

🧪 Simulador · marcadores, reinício e replay

O Analytics guarda o painel na memória do programa. O marcador fica no Kafka. Siga o roteiro: publique 4 pagamentos → Analytics lê tudo → reinicie o Analytics → observe o painel → rebobine.

grupo analytics

marcador
0
lag
0
painel (memória)
0 pedidos · R$ 0,00

grupo auditoria

marcador
0
lag
0
painel (memória)
0 pedidos · R$ 0,00

Publique alguns pagamentos para começar.

Antes de revelar: o Analytics reiniciou. O painel mostra 0 pedidos, mas o tópico tem 4. Por quê?

Parte 1 · dividir para escalar 14:30 · 15 min

Partições e chave: onde fica a ordem [07]

Por que dividir o caderno

Um único caderno tem um limite: só uma pessoa escreve e lê por vez naquele ritmo. Para crescer, o Kafka divide cada tópico em partições: vários logs menores, lado a lado. Cada partição tem seus próprios offsets, começando em 0. Hoje o tópico pedidos-pagos terá 3 partições.

Dentro de um grupo, cada partição é lida por um só consumidor por vez. Com 3 partições, um grupo pode ter até 3 consumidores trabalhando em paralelo. Um quarto consumidor no mesmo grupo fica ocioso, esperando: não há partição sobrando para ele.

A chave escolhe a partição

Quando o produtor envia um registro com chave, o Kafka faz uma conta com a chave (um hash) e escolhe a partição. Mesma chave, mesma conta, mesma partição. Hoje a chave é o pedidoId.

E aqui está a regra mais importante do dia: o Kafka só garante ordem dentro de uma partição. Entre partições diferentes, não há ordem. Se todos os eventos do pedido 4711 usam a chave 4711, todos caem na mesma partição e são lidos na ordem em que foram escritos. Sem chave, eles se espalham, e “enviado” pode ser processado antes de “pago”.

🎮 Mundo 2 · As três pontas do mesão (1 min, com som). Com chave, sem chave e um quarto colega sem partição. Depois, repita no simulador.
Transcrição do vídeo
  1. Tico: Fase dois: as três pontas do mesão. O tópico agora tem três partições.
  2. Tico: Cada partição é uma ponta da mesa, com seus próprios registros e offsets.
  3. Lia: E como o Kafka escolhe a ponta de cada pedido?
  4. Tico: Pela chave. Mesma chave, mesma partição. Aqui, a chave é o número do pedido.
  5. Beto: Pedido A: criado, pago, enviado. Tudo na partição um, em ordem. Perfeito!
  6. Tico: Agora, sem chave. O produtor espalha os registros pelas pontas.
  7. Lia: Eita! Eu li "A enviado" antes de o Beto ler "A pago"!
  8. Beto: Enviou antes de pagar? Isso vai dar problema!
  9. Tico: Ordem só existe dentro da partição. Por isso a chave importa.
  10. Lia: E esse quarto colega aí, parado?
  11. Tico: Três partições, quatro consumidores no mesmo grupo. Um fica de reserva, sem partição.

🧪 Simulador · partições, chave e consumidores

Publique eventos do mesmo pedido (criado → pago → enviado). Compare com chave e sem chave. Depois mude o número de consumidores do grupo.

Como o produtor envia

Publique o primeiro evento do pedido A.

A conta de partição aqui é simplificada para caber na tela. O Kafka real usa um hash (murmur2) da chave; a regra “mesma chave, mesma partição” é a mesma.

Antes de revelar: 3 partições, 5 consumidores no mesmo grupo. Quantos trabalham?

Parte 1 · vocabulário 14:45 · 5 min

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

Você já viu todas. Este quadro é para consultar, não para decorar. A última coluna liga cada peça ao que você já conhece do RabbitMQ.

PeçaFunçãoNo RabbitMQ, o mais parecido
BrokerO servidor do Kafka. Guarda os tópicos em disco.O próprio RabbitMQ
TópicoUm log com nome, por assunto: pedidos-pagos.Exchange + filas (mas nada sai ao ler)
PartiçãoUm pedaço do tópico. Tem ordem própria e offsets próprios.Sem equivalente direto
OffsetA posição de um registro dentro da partição.Sem equivalente: a fila não numera
ProdutorQuem escreve. Escolhe tópico e chave.Quem publica na exchange
Grupo de consumidoresQuem lê, com um marcador por partição.Uma fila com seus consumidores
Parte 2 · 14:50–15:55 · sem Kafka

Loja Café: primeiro sem Kafka

Vamos construir com o agente um sistema pequeno e de verdade: uma vitrine em React, uma API em Spring e um serviço de pagamentos em Python. Primeiro do jeito mais comum, com uma chamada direta. Quando o problema aparecer na tela, o Kafka entra. O desenho da arquitetura acompanha cada ato.

Parte 2 · o que vamos construir 14:50 · 10 min

A Loja Café, peça por peça [09]

Na Parte 1 você viu o caderno funcionando nos simuladores. Agora ele vai trabalhar num sistema. É uma cafeteria: o cliente escolhe no cardápio, a API grava o pedido e alguém precisa cobrar. Nada aqui é produção. O objetivo é ver, num sistema que você mesmo montou, onde o Kafka faz diferença.

PeçaTecnologiaPortaPapelNasce no
front/React + Vite5173Cardápio e lista de pedidos com statusAto 1
pedidos-api/Spring Boot + H28080Recebe, grava e informa os pedidosAto 1
pagamentos-py/Python + Flask5001Decide se aprova (simulado, demora 3 s)Ato 2
infra/Kafka + painel (Docker)9092 · 8090O caderno entre as peçasAto 3
analytics-py/Script Python—Painel de vendas que chega depoisAto 4

Use os botões para ver o sistema crescer. Em cada ato este desenho volta, com a parte nova acesa. O botão ▶ Ver um pedido passar anima o caminho de um pedido naquele ato.

Por que duas linguagens?

Num sistema real, cada time escolhe a sua ferramenta. Pagamentos em Python e Pedidos em Java não é capricho: é o cenário mais comum quando várias equipes trabalham no mesmo negócio. Se a conversa entre as peças for JSON com um contrato escrito, a linguagem de cada lado não importa. Vale para HTTP no Ato 2 e para o Kafka no Ato 3.

Prepare a bancada

  1. Baixe o ponto de partida: loja-cafe-inicio.zip. Descompacte numa pasta sua, fora de qualquer outro projeto. Ele traz só o combinado, ainda sem código:
    loja-cafe/conteúdo do ZIP
    AGENTS.md        regras para o agente (portas, pastas, "não invente contrato")
    CLAUDE.md        uma linha: @AGENTS.md
    COMO-RODAR.md    o comando de cada peça
    specs/api.md         contrato REST da API de pedidos
    specs/pagamentos.md  regra do pagamento simulado
    specs/eventos.md     contrato dos eventos (só no Ato 3)
    infra/compose.yml    Kafka + painel, pronto
  2. Confira as ferramentas num terminal. Cada comando deve responder uma versão:
    seu terminal
    java -version
    node -v
    python3 --version
    docker version

    Esperado: Java 21, Node 20 ou mais novo, Python 3.10 ou mais novo, e o Docker mostrando Server. No Windows, troque python3 por py.
  3. Abra o agente dentro de loja-cafe/ (Claude Code, Codex ou Antigravity). Os prompts de hoje funcionam nos três, porque as regras estão no AGENTS.md.
Ato 1 · 👥 junto · 🧭 dirigir o agente 15:00 · 15 min

A API de pedidos, em Spring [10]

O que a API faz, e o que ainda não faz

A API recebe um pedido por HTTP, em JSON, calcula o valor, grava e devolve o pedido com 201 Created. Para guardar, usamos o H2: um banco de dados que roda dentro do próprio programa Java e salva num arquivo da pasta. Nada de Docker nem de Postgres neste ato.

Todo pedido nasce AGUARDANDO_PAGAMENTO. Neste ato ninguém cobra, e é de propósito: queremos o sistema mais simples que funciona, para depois acrescentar uma coisa de cada vez.

Prompt 1 · pedidos-api (Spring)
[CONTEXTO] Estou na pasta loja-cafe. Leia AGENTS.md e specs/api.md. Hoje criamos só a pedidos-api; front e pagamentos vêm depois.
[TAREFA] Gere o projeto pelo Spring Initializr (start.spring.io, versão padrão do Spring Boot, Java 21, Maven) na pasta pedidos-api, pacote br.com.lojacafe.pedidos, com Spring Web, Spring Data JPA, H2 e Validation. Implemente exatamente o contrato de specs/api.md: entidade Pedido, repositório, POST /pedidos (valor = quantidade × precoUnitario, nasce AGUARDANDO_PAGAMENTO) e GET /pedidos (mais novo primeiro). H2 em arquivo (jdbc:h2:file:./dados/pedidos) para não perder os pedidos ao reiniciar. CORS liberado para http://localhost:5173.
[LIMITES] Sem camadas extras, sem DTO de saída (devolva a entidade), sem Lombok. Não crie front nem pagamentos.
[ACEITE] Rode pelo Maven Wrapper. Mostre um POST com resposta 201 e valor calculado, um POST inválido com 400 e o GET listando.
[PAUSA] Mostre os arquivos que vai criar e aguarde meu OK.
[COMMIT] Depois do meu OK sobre o resultado: git init (se ainda não houver) e commit "Ato 1: pedidos-api".

Prove você mesmo. Com a API rodando, num terminal novo:

seu terminal · macOS/Linux (no Windows, use a collection do Insomnia)
curl -s -X POST localhost:8080/pedidos -H "Content-Type: application/json" -d '{"cliente":"Ana","item":"Café coado","quantidade":2,"precoUnitario":8.50}'

resposta esperada (a ordem dos campos pode variar)201
{"id":1,"cliente":"Ana","item":"Café coado","quantidade":2,"valor":17.0,
 "status":"AGUARDANDO_PAGAMENTO","motivo":null,"processadoPor":null,"criadoEm":"2026-10-17T..."}
👀 Referência: o controller que funciona (para revisar o do agente)
pedidos-api · PedidoController.javaAto 1
@PostMapping
@ResponseStatus(HttpStatus.CREATED)
public Pedido criar(@Valid @RequestBody NovoPedido novo) {
    Pedido pedido = new Pedido(novo.cliente(), novo.item(), novo.quantidade(), novo.precoUnitario());
    return repository.save(pedido);
}

@GetMapping
public List<Pedido> listar() {
    return repository.findAllByOrderByIdDesc();
}

NovoPedido é um record com as validações do contrato (@NotBlank, @Min(1) @Max(50), @Positive). O @Valid faz o Spring devolver 400 sozinho.

O valor é calculado no construtor de Pedido, com BigDecimal. O front nunca manda o total: quem calcula preço é o servidor.

Ato 1 · 👥 junto · 🧭 dirigir o agente 15:15 · 15 min

A vitrine, em React [11]

Três ideias antes do prompt

Vite é a ferramenta que cria e roda o projeto React no navegador, em localhost:5173. React desenha a tela a partir do estado: mudou a lista de pedidos, a tela se redesenha.

CORS é uma regra do navegador: uma página aberta em localhost:5173 só pode chamar localhost:8080 se a API autorizar essa origem. Por isso o Prompt 1 já liberou o CORS. Sem isso, o erro aparece no console do navegador, não no Java.

Para ver o status mudar sem recarregar, a tela pergunta à API a cada 2 segundos (polling). Existem formas mais elegantes, como WebSocket, mas seriam outro assunto. Aqui a simplicidade ganha.

Prompt 2 · front (React)
[CONTEXTO] A pedidos-api já roda na 8080 (specs/api.md). Agora a vitrine.
[TAREFA] Crie front/ com Vite + React em JavaScript (npm create vite@latest front -- --template react). Uma página só: campo nome do cliente; cardápio com 4 itens fixos (Café coado 8,50 · Cappuccino 12,00 · Pão de queijo 6,00 · Bolo de cenoura 9,50); quantidade de 1 a 50; total calculado; botão Fazer pedido que fica "Enviando…" enquanto espera. Depois de cada envio, mostre "A API respondeu em N ms" (meça com performance.now()). Se der erro, mostre uma faixa vermelha com o status. Abaixo, a lista de pedidos com id, cliente, item, valor, status colorido (amarelo aguardando, verde pago, vermelho recusado), motivo e processadoPor, atualizada a cada 2 segundos pelo GET /pedidos.
[LIMITES] Sem TypeScript, sem biblioteca de UI, sem roteador, sem gerenciador de estado. Chamadas à API em src/api.js, com a URL http://localhost:8080. Não mexa na pedidos-api.
[ACEITE] npm run dev abre em http://localhost:5173; um pedido feito na tela aparece na lista como "Aguardando pagamento" e o tempo de resposta aparece.
[PAUSA] Aguarde meu OK antes de instalar dependências.
[COMMIT] Depois do meu OK: commit "Ato 1: front".

  1. Abra localhost:5173, digite seu nome e faça um pedido.
  2. Esperado: o pedido aparece no topo da lista, em amarelo, Aguardando pagamento. Embaixo do botão: A API respondeu em ~30 ms. Anote esse número. Ele vai mudar no Ato 2 e voltar no Ato 3.
👀 Referência: o pedaço que faz o status mudar sozinho
front · src/App.jsxpolling a cada 2 s
// Pergunta à API a cada 2 segundos. Assim o status muda na tela sozinho.
useEffect(() => {
  const atualizar = () => listarPedidos().then(setPedidos).catch(() => {})
  atualizar()
  const id = setInterval(atualizar, 2000)
  return () => clearInterval(id)
}, [])

Antes de seguir: quem está cobrando os pedidos agora?

Ato 2 · 👥 junto · 🧭 dirigir o agente 15:30 · 15 min

Pagamento por telefone: Flask via HTTP [12]

Chamada síncrona: a API liga e fica na linha

O jeito mais comum de duas peças conversarem é uma chamada HTTP direta. A API grava o pedido, liga para o Pagamentos e espera na linha até ele responder. Só então devolve a resposta para o front. Isso se chama chamada síncrona: quem chama fica parado até a resposta chegar.

O Pagamentos é simulado em Python com Flask, um framework web mínimo: um arquivo, uma função por rota. Ele “fala com o banco” por 3 segundos (time.sleep(3)) e aprova até R$ 100,00. A regra está em specs/pagamentos.md.

Prompt 3 · pagamentos-py por HTTP
[CONTEXTO] Leia specs/pagamentos.md. Vamos criar o Pagamentos simulado, em Python, chamado pela API por HTTP.
[TAREFA] Crie pagamentos-py/ com .venv, requirements.txt (flask) e app.py: POST /pagamentos recebe {pedidoId, valor}, espera 3 segundos e responde APROVADO até R$ 100,00 ou RECUSADO com motivo "limite do cartão de teste"; GET /saude. Nome da instância em INSTANCIA (padrão caixa-1), porta em PORTA (padrão 5001). Imprima uma linha por decisão. Depois, na pedidos-api: logo após gravar o pedido, chame POST http://localhost:5001/pagamentos com RestClient, espere a resposta e atualize status, motivo e processadoPor antes de responder ao front.
[LIMITES] Não trate a falha do Pagamentos (sem try/catch, retry ou timeout especial): queremos ver o que acontece. Não mexa no front.
[ACEITE] Com os dois rodando: um pedido de R$ 17,00 volta PAGO na própria resposta, em cerca de 3 s; um de 20 cafés volta RECUSADO.
[PAUSA] Mostre o plano e aguarde meu OK.
[COMMIT] Depois do meu OK: commit "Ato 2: pagamento por HTTP".

Para rodar o Flask (o agente pode fazer isso por você), num terminal só dele:

terminal do Pagamentos · macOS/Linux
cd pagamentos-py
python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
python app.py

No Windows, troque as linhas 2 e 3 por py -m venv .venv e .venv\Scripts\activate. O .venv é uma caixa só deste serviço para as bibliotecas Python; assim o Flask daqui não briga com nada instalado no seu computador.

  1. Reinicie a API (ela mudou) e faça um pedido no front. O botão fica Enviando… por uns 3 segundos. Depois: A API respondeu em ~3 050 ms, e o pedido já chega Pago, com a etiqueta caixa-1.
  2. Peça 20 cafés coados (R$ 170,00). Volta Recusado, com o motivo.
👀 Referência: a chamada que prende o cliente
pedidos-api · PedidoController.javaAto 2
pedido = repository.save(pedido);

// Ato 2: o cliente fica esperando enquanto o Pagamentos decide (~3 s).
// Se o Pagamentos estiver fora, a exceção sobe e o cliente recebe 500,
// mas o pedido já foi gravado e fica AGUARDANDO para sempre.
PagamentosClient.Resposta resposta = pagamentos.pagar(pedido);
pedido.concluir("APROVADO".equals(resposta.status()), resposta.motivo(), resposta.instancia());
return repository.save(pedido);
Ato 2 · 👥 junto · quebre de propósito 15:45 · 10 min

Desligue o Pagamentos: o problema do telefone [13]

Funciona. Agora vamos ver o que acontece num dia ruim.

  1. Pare o Flask (Ctrl+C no terminal dele) e faça um pedido no front.
  2. Esperado: faixa vermelha, POST /pedidos respondeu 500. O cliente acha que não comprou.
  3. Olhe a lista: o pedido está lá, Aguardando pagamento. E vai ficar assim para sempre, porque ninguém vai tentar cobrar de novo. O cliente viu erro, a loja tem um pedido órfão.

Antes de revelar: e se amanhã o Estoque, o E-mail e o Analytics também quiserem saber de cada pedido?

A API ligando para quatro serviços em sequência Pedidos · APIespera todos Pagamentos · 3 s Estoque · fora ✗ E-mail · 2 s Analytics · 1 s resposta ao cliente = soma das esperas um fora = o pedido inteiro falha 99% × 99% × 99% × 99% ≈ 96% no ar ≈ 29 horas por mês fora, em vez de 7
Acoplamento temporal. Quem chama e quem atende precisam estar vivos ao mesmo tempo. Cada serviço novo piora o tempo de resposta e a disponibilidade da API.

Volte ao ponto 04

Nas duas cozinhas do vídeo, o garçom não fica parado na porta esperando o prato. Ele anota o pedido (na comanda ou no caderno) e volta a atender; a cozinha pega quando puder. Quem escreve não espera quem lê. É isso que falta à nossa API: hoje ela é um garçom que liga para a cozinha e fica na linha. No Ato 3 ela passa a anotar no caderno do Kafka. Ligue o Flask de novo antes do intervalo.

☕ 15:55–16:10

Intervalo

Na volta, o telefone sai e o caderno entra. Deixe a API e o front rodando.

Parte 3 · 16:10–17:10 · com Kafka

O caderno entra no meio

Mesmo sistema, mesma tela. Só muda a forma como as peças conversam. Depois, o que o Kafka faz que a fila do Cap 02 não faria.

Ato 3 · 👥 junto · um comando e um contrato 16:10 · 10 min

Suba o Kafka e combine os eventos [14]

O compose já está no ZIP inicial. Ele sobe só o Kafka e o painel; a API, o front e o Flask continuam rodando no seu computador.

seu terminal, dentro de loja-cafe/infra
docker compose up -d --wait

Esperado: Container loja-cafe-kafka-1 Healthy e Container loja-cafe-kafka-ui-1 Healthy. Abra o painel em localhost:8090: o cluster aula aparece online, ainda sem tópicos seus.

Por que o Kafka tem duas portas (9092 e 19092)?

Endereço anunciado

Quando um programa se conecta ao Kafka, o broker responde “para falar comigo, use este endereço”. Esse endereço anunciado se chama advertised listener. A API e o Flask rodam no seu computador e encontram o Kafka em localhost:9092. O painel roda em outro container, onde localhost é ele mesmo; lá dentro o Kafka se chama kafka. Por isso há duas portas, cada uma anunciando o endereço certo para quem está daquele lado. Regra prática: no seu computador, localhost:9092; dentro de um container, kafka:19092.

O contrato dos eventos (specs/eventos.md)

Na Parte 1, o exemplo era um tópico só, pedidos-pagos. Na Loja Café são dois, um em cada direção: pedidos-criados (a API conta que um pedido nasceu) e pagamentos-resultados (o Pagamentos conta o que decidiu).

tópico pedidos-criados · quem publica: pedidos-apichave = pedidoId
chave: "12"
valor: { "versao": 1, "pedidoId": 12, "cliente": "Ana", "item": "Café coado",
         "quantidade": 2, "valor": 17.00, "criadoEm": "2026-10-17T16:05:00Z" }
tópico pagamentos-resultados · quem publica: pagamentos-pymesma chave
chave: "12"
valor: { "versao": 1, "pedidoId": 12, "status": "APROVADO", "motivo": null,
         "instancia": "caixa-1", "processadoEm": "2026-10-17T16:05:03Z" }
DecisãoEscolhaPor quê
ChavepedidoId nos dois tópicosTudo do mesmo pedido na mesma partição, em ordem (ponto 07).
ValorJSON como textoJava escreve, Python lê, e vice-versa. Ninguém precisa das classes do outro.
Versão"versao": 1Produtor e leitores mudam em dias diferentes.
Grupospagamentos, pedidos-api, analyticsCada leitor tem seu marcador (ponto 06).
Inícioauto.offset.reset = earliestGrupo que nunca leu começa do offset 0. Vai ser a estrela do Ato 4.
Ato 3 · 👥 junto · 🧭 dirigir o agente 16:20 · 20 min

Troque o telefone pelo caderno [15]

Dois prompts, um para cada lado. A API passa a anotar o pedido no tópico e responder na hora. O Pagamentos deixa de ser chamado: ele lê o tópico, decide e anota o resultado em outro tópico, que a API lê para atualizar o status.

Prompt 4 · pedidos-api publica e escuta
[CONTEXTO] Leia specs/eventos.md. O Kafka está rodando (infra/compose.yml, localhost:9092). Vamos trocar a chamada HTTP ao Pagamentos por eventos.
[TAREFA] Na pedidos-api: adicione o starter de Kafka do Spring Boot. Crie os tópicos pedidos-criados e pagamentos-resultados (3 partições, 1 réplica) com beans NewTopic. Depois de gravar o pedido, publique PedidoCriado em pedidos-criados com chave = pedidoId e valor = JSON em texto, e espere a confirmação do broker (get com 5 s). Remova a chamada HTTP ao Pagamentos. Crie um @KafkaListener em pagamentos-resultados (grupo pedidos-api, auto-offset-reset earliest) que atualiza status, motivo e processadoPor; se o pedido já não estiver AGUARDANDO, ignore (resultado repetido). Configure max.block.ms=5000 no produtor.
[LIMITES] Chave e valor como String. Converta o JSON com o JsonMapper do próprio Spring Boot; não use o JsonSerializer do Spring Kafka (ele grava o tipo Java num header e o Python não precisa disso). Não mexa no front.
[ACEITE] POST /pedidos responde em menos de 1 s com AGUARDANDO_PAGAMENTO, e o tópico pedidos-criados aparece em http://localhost:8090 com a mensagem do pedido.
[PAUSA] Mostre o plano e aguarde meu OK.
[COMMIT] Depois do meu OK: commit "Ato 3: pedidos-api no Kafka".

Prompt 5 · pagamentos-py lê e publica
[CONTEXTO] Leia specs/eventos.md. A pedidos-api já publica em pedidos-criados. Agora o Pagamentos deixa de ser chamado: ele lê.
[TAREFA] Em pagamentos-py, adicione confluent-kafka. Numa thread separada, um consumidor do grupo pagamentos (auto.offset.reset earliest, broker.address.family v4) lê pedidos-criados, aplica a mesma regra (3 s, limite R$ 100) e publica o resultado em pagamentos-resultados com a mesma chave, no formato do contrato, incluindo instancia. Produtor com partitioner murmur2_random. Imprima partição, offset, pedido e decisão. Remova o POST /pagamentos; mantenha GET /saude e crie GET /pagamentos com o que esta instância processou. Rode o Flask com use_reloader=False e, no Ctrl+C, feche o consumidor.
[LIMITES] Sem Celery, sem asyncio, sem framework extra. Não mexa na pedidos-api nem no front.
[ACEITE] Com tudo rodando, um pedido feito no front aparece Aguardando e vira Pago em cerca de 3 s, sozinho. O log mostra partição e offset.
[PAUSA] Mostre o plano e aguarde meu OK.
[COMMIT] Depois do meu OK: commit "Ato 3: pagamentos lê do Kafka".

  1. Reinicie a API e o Flask (instale antes a dependência nova: pip install -r requirements.txt, com o .venv ativo).
  2. Não mexa no front. Faça um pedido. Esperado: A API respondeu em ~20 ms, o pedido aparece Aguardando e, uns 3 segundos depois, vira Pago sozinho.
  3. No terminal do Flask:
    terminal do Pagamentosexemplo real
    [caixa-1] ouvindo pedidos-criados no grupo 'pagamentos'
    [caixa-1] partição 0 · offset 0 · pedido 5 -> APROVADO
  4. No painel: Topics → pedidos-criados → Messages. O pedido está lá, com a chave igual ao id. Em pagamentos-resultados, a decisão.
👀 Referência: os três trechos que importam
pedidos-api · PedidoEventos.java · publicaKafkaTemplate
var evento = new PedidoCriado(1, p.getId(), p.getCliente(), p.getItem(),
        p.getQuantidade(), p.getValor(), p.getCriadoEm());
// Chave = pedidoId: todos os eventos do mesmo pedido caem na mesma partição.
// .get() espera o "ok" do broker; se o Kafka estiver fora, a falha aparece aqui.
kafka.send("pedidos-criados", String.valueOf(p.getId()), json.writeValueAsString(evento))
        .get(5, TimeUnit.SECONDS);
pedidos-api · ResultadoPagamentoListener.java · escuta@KafkaListener
@KafkaListener(topics = "pagamentos-resultados")
public void receber(String mensagem) {
    var resultado = json.readValue(mensagem, ResultadoPagamento.class);
    repository.findById(resultado.pedidoId()).ifPresent(pedido -> {
        // concluir() ignora repetição: o mesmo resultado duas vezes não muda nada.
        if (pedido.concluir("APROVADO".equals(resultado.status()), resultado.motivo(), resultado.instancia())) {
            repository.save(pedido);
        }
    });
}
pagamentos-py · app.py · o laço do consumidorconfluent-kafka
while not parar.is_set():
    msg = consumidor.poll(1.0)          # espera até 1 s por uma mensagem
    if msg is None:
        continue
    pedido = json.loads(msg.value())
    status, motivo = decidir(float(pedido["valor"]))
    produtor.produce("pagamentos-resultados", key=str(pedido["pedidoId"]), value=json.dumps(resultado))
    produtor.flush()

O grupo da API não aparece no código: vem de spring.kafka.consumer.group-id=pedidos-api. Quando o método termina sem erro, o Spring registra o offset do grupo.

No Python, o commit do offset é automático, a cada 5 s. Se o processo morrer no meio, o grupo pode reler uma mensagem: por isso a API ignora resultado repetido.

Antes de seguir: quantas linhas do front você mudou neste ato?

Ato 3 · 👥 junto · o mesmo teste do Ato 2 16:40 · 10 min

Desligue o Pagamentos de novo [16]

  1. Pare o Flask (Ctrl+C) e faça três pedidos seguidos no front.
  2. Esperado: nenhuma faixa vermelha. A API responde em milissegundos e os três ficam Aguardando.
  3. No painel: Consumers → pagamentos. A coluna Consumer Lag mostra 3: três registros escritos que o grupo ainda não leu.
  4. Ligue o Flask. Ele lê os três, um a cada 3 segundos, imprimindo partição e offset. Na tela, os três viram Pago. O lag volta a 0.
Mesma perguntaAto 2 · telefone (HTTP)Ato 3 · caderno (Kafka)
Quanto o cliente espera?~3 s, o tempo do Pagamentos~20 ms, o tempo de anotar
Pagamentos fora do arErro 500 e pedido órfãoPedido aceito; cobra quando voltar
Quem a API conhece?O endereço do PagamentosSó o Kafka e dois tópicos
Um interessado novoMudar a API e esperar mais umUm grupo novo lendo o tópico
O que piorou—O status não vem na resposta: a tela precisa perguntar depois
Ato 4 · 👥 junto · o diferencial do Kafka 16:50 · 15 min

Quem chega depois lê o passado [17]

Por que isto é diferente do Cap 02

É o Tico do vídeo do ponto 04, o contador que só chegou no fim do mês: na cozinha da comanda ele não teria o que ler; na do caderno, lê desde a linha zero. No RabbitMQ, uma fila nova só recebe o que for publicado depois que ela existe. O que já foi entregue e confirmado saiu da fila. No Kafka, ler não apaga (ponto 05). Um grupo novo não tem marcador, e com earliest começa do offset 0: lê a história inteira, no próprio ritmo, sem atrapalhar os outros grupos (ponto 06).

Prompt 6 · analytics-py
[CONTEXTO] Leia specs/eventos.md. Já existem pedidos no Kafka. Quero um serviço novo que chega agora, sem que ninguém o avise.
[TAREFA] Crie analytics-py/ com .venv, requirements.txt (confluent-kafka) e analytics.py: um consumidor com group.id vindo da variável GRUPO (padrão analytics), auto.offset.reset earliest e broker.address.family v4, lendo pedidos-criados e pagamentos-resultados. Guarde num dicionário em memória, indexado por pedidoId, item, quantidade, valor e status (vale o primeiro status recebido). Para cada mensagem, imprima tópico, partição, offset e pedido; depois de cada lote, um painel: pedidos vistos, pagos, faturamento (soma dos aprovados) e item mais vendido. Ctrl+C fecha o consumidor.
[LIMITES] Sem banco, sem Flask, sem arquivo: o painel vive só na memória, de propósito. Não mexa em nenhuma outra pasta.
[ACEITE] Na primeira execução, o painel mostra os pedidos feitos antes de o Analytics existir.
[PAUSA] Mostre o plano e aguarde meu OK.
[COMMIT] Depois do meu OK: commit "Ato 4: analytics".

  1. Primeira execução. Em analytics-py, com o .venv ativo: python analytics.py. Ele lê tudo desde o offset 0:
    terminal do Analyticsexemplo · os seus números serão outros
    Analytics lendo no grupo 'analytics'. Ctrl+C para sair.
      pedidos-criados        partição 0 · offset   0 · pedido 1
      pagamentos-resultados  partição 0 · offset   0 · pedido 1
      pedidos-criados        partição 2 · offset   0 · pedido 2
      ...
    📊 pedidos vistos: 9 · pagos: 7 · faturamento: R$ 94.00 · mais vendido: Café coado (6)
    Confira com a tela do front: os números batem com os pedidos que você fez antes de o Analytics existir.
  2. Faça um pedido novo no front. O Analytics imprime a linha e o painel sobe, ao vivo.
  3. Feche (Ctrl+C) e rode de novo. Antes de rodar, responda:

    O que o painel vai mostrar na segunda execução?

  4. Rebobine. Com o Analytics parado (o reset exige grupo sem ninguém lendo), em infra/:
    seu terminal, em loja-cafe/infra
    docker compose exec kafka /opt/kafka/bin/kafka-consumer-groups.sh --bootstrap-server kafka:19092 --group analytics --all-topics --reset-offsets --to-earliest --execute

    Rode o Analytics de novo: o painel inteiro volta, igual. Isso é replay. O painel é uma visão derivada do log: pode ser jogado fora e reconstruído.
  5. Um time novo. Rode com outro grupo: GRUPO=auditoria python analytics.py (PowerShell: $env:GRUPO="auditoria"; python analytics.py). Lê tudo, com marcador próprio, sem mexer no do analytics.
PeçaLinhas mudadas para o Analytics existir
Front0
pedidos-api0
pagamentos-py0
Kafka0 (nem criamos tópico)
Ato 5 · 👥 junto · se der tempo (senão, em casa) 17:05 · 5 min

Dois caixas no mesmo grupo [18]

É o simulador do ponto 07, de verdade. Dois consumidores no mesmo grupo dividem as partições: cada pedido é cobrado por um caixa só, e o trabalho anda em paralelo. Não muda uma linha de código, só sobe outra instância.

terminal novo, em pagamentos-py, com o .venv ativo · macOS/Linux
INSTANCIA=caixa-2 PORTA=5002 python app.py

No PowerShell: $env:INSTANCIA="caixa-2"; $env:PORTA="5002"; python app.py.

  1. Faça seis pedidos rápidos. Na lista, as etiquetas alternam entre caixa-1 e caixa-2, conforme a partição de cada pedido.
  2. No painel: Consumers → pagamentos. Duas instâncias, cada uma com as suas partições (com 3 partições, uma fica com duas).
  3. Ctrl+C no caixa-2. Em poucos segundos o grupo se rebalanceia e o caixa-1 volta a cuidar das três partições.

Antes de revelar: e se você subir o caixa-3 e o caixa-4?

Parte 4 · 17:10–17:30

Quando usar qual

Em sala: o quadro, o vídeo do lutador e as propostas do agente. A falha do Kafka fica para casa, com o vídeo do chefão.

🏠 Em casa · com o agente · provoque a falha

Kafka desligado: o pedido existe, o evento não [19]

Escrita dupla

No Ato 3 a API faz duas escritas: grava o pedido no H2 e depois publica no Kafka. Não existe uma transação que una as duas. Se o Kafka estiver fora nesse intervalo, o banco já mudou e o evento não saiu.

No nosso código a falha não passa em silêncio: o .get(5 s) lança exceção e o front recebe 500 depois de uns 5 segundos. Mas o pedido já foi gravado, sem evento. Ninguém vai cobrá-lo. É o pedido órfão do Ato 2, voltando por outra porta.

Janela entre gravar no H2 e publicar no Kafka POST /pedidoschega do front H2: pedido salvogravou ✓ Kafka forapublicação falha ✗ front: 500pedido órfão janela da escrita dupla Com o Kafka de volta, os pedidos NOVOS funcionam. O órfão continua AGUARDANDO: alguém precisa republicar o evento dele.
Não há mágica. Sem um mecanismo extra, o evento perdido não volta sozinho.
🎮 Mundo 2 · Chefão: a escrita dupla (50 s, com som). O vídeo usa o sistema do Cap 02 (Postgres e RabbitMQ); a lição é a mesma da Loja Café: duas escritas, nenhuma transação entre elas.
Transcrição do vídeo
  1. Tico: Chefão do mundo dois: a escrita dupla!
  2. Beto: Pagamento aprovado. Gravei PAGO no Postgres. Agora é só avisar o Kafka...
  3. Beto: O Kafka está fora do ar! A mensagem não sai!
  4. Lia: Peraí: o pedido está pago, mas o meu painel não sabe de nada.
  5. Tico: São duas escritas, sem uma transação que una as duas. O chefão mora nesse intervalo.
  6. Tico: Por sorte, o RabbitMQ tentou três vezes e guardou o resultado na fila de erros.
  7. Beto: O Kafka voltou! Vou reenviar o mesmo resultado, com a mesma chave.
  8. Lia: Chegou! Pedido pago no painel. Chefão derrotado!
  9. Tico: Próximo mundo: o outbox. O evento é gravado no banco, na mesma transação, e publicado depois.
  1. Em infra/, pare só o Kafka: docker compose stop kafka.
  2. Faça um pedido. O botão fica Enviando… uns 5 segundos e aparece a faixa vermelha (500). Na lista, o pedido está lá, Aguardando.
  3. Suba o Kafka: docker compose up -d --wait. Faça outro pedido: esse funciona. O órfão continua Aguardando.
  4. Peça ao agente o diagnóstico e uma proposta de correção (Prompt 7). Não deixe ele implementar antes de você entender.
Prompt 7 · diagnóstico e outbox
[CONTEXTO] Desliguei o Kafka, fiz um pedido, recebi 500 e liguei o Kafka de novo. O pedido ficou AGUARDANDO_PAGAMENTO para sempre.
[TAREFA] Sem editar arquivos, explique com evidências o que aconteceu: o pedido no GET /pedidos, as linhas de erro do log da API e as mensagens do tópico pedidos-criados (o pedido órfão não está lá). Depois proponha o padrão outbox para este projeto: tabela, momento da gravação, quem publica, de quanto em quanto tempo, e o que acontece se publicar duas vezes.
[LIMITES] Não use down -v, não apague dados, não reinicie nada sem me perguntar.
[ACEITE] Diagnóstico em cinco frases, com a evidência de cada uma. Proposta de outbox em até dez linhas, citando por que a pedidos-api já aguenta evento repetido.
[PAUSA] Aguarde meu OK antes de qualquer implementação.

Parte 4 · ⚖️ defender ou refutar 17:10 · 10 min

RabbitMQ ou Kafka? Depende da pergunta [20]

Nenhum dos dois é “melhor”. São modelos diferentes. Este é o quadro que você leva para o resto do curso. Na AWS, mais adiante, vamos acrescentar uma terceira coluna.

CritérioRabbitMQ (fila)Kafka (log)
Depois de lidaA mensagem sai da filaO registro fica até vencer a retenção
Leitor novoSó recebe o que chegar depois de sua fila existirPode ler desde o início
ReprocessarAlguém precisa publicar de novoRebobinar o marcador do grupo
RoteamentoRico: exchanges, bindings, routing keysSimples: por tópico; filtro fica no leitor
OrdemPor fila, enquanto houver um consumidorPor partição, escolhida pela chave
ParalelismoMais consumidores na mesma filaAté o número de partições, por grupo
Melhor paraComandos e tarefas a distribuirEventos de negócio, histórico, vários leitores
🎮 Mundo 2 · Escolha seu lutador (1 min 12 s, com som). Cada round tem 3 segundos para a turma responder antes do Beto. Os cenários são outros; os do agente vêm logo abaixo.
Transcrição do vídeo
  1. Tico: Modo arcade: escolha seu lutador!
  2. Tico: De um lado, a fila: entrega e esquece, roteamento afiado. Do outro, o log: guarda tudo e aceita replay.
  3. Lia: Rodada um: cinco impressoras dividindo notas fiscais. Cada nota sai uma vez só.
  4. Beto: Fila! É trabalho pra distribuir, ninguém precisa reler.
  5. Lia: Rodada dois: o time de BI chega mês que vem e quer todo o histórico.
  6. Beto: Log! Quem chega depois lê desde o começo.
  7. Lia: Rodada três: um alerta só para as lojas do Nordeste.
  8. Beto: Fila! Roteamento por chave é o forte do RabbitMQ.
  9. Lia: Rodada quatro: três times querem os mesmos cliques do app, cada um no seu ritmo.
  10. Beto: Log! Cada time é um grupo, com o seu próprio marcador.
  11. Tico: Não existe lutador melhor. Existe o lutador certo pra cada fase.

O agente fez quatro propostas. Para cada uma, decida se você defende ou refuta, e diga a frase que usaria na revisão.

Cenário 1 · “Troque o RabbitMQ das solicitações de pagamento por Kafka, porque Kafka escala mais.”

Cenário 2 · “A comissão dos vendedores mudou. Use o Kafka para recalcular os últimos 30 dias de pedidos pagos.”

Cenário 3 · “Pagamentos com cartão vão para um serviço, Pix para outro, e os acima de R$ 1.000 também para o antifraude. Faça isso com Kafka.”

Cenário 4 · “Publique PedidoPago sem chave, para espalhar melhor entre as partições.”

Fechamento · cinco perguntas 17:20 · 10 min

Explique o sistema sem decorar comandos [21]

Antes de revelar: qual frase resume a diferença do dia?

  1. No Ato 2, por que o cliente esperou 3 segundos e por que o pedido ficou órfão quando o Flask caiu? O que mudou no Ato 3?
  2. Em que momento da tarde o Kafka fez algo que a fila do Cap 02 não faria?
  3. Onde fica o marcador do grupo analytics, e por que rodar o Analytics de novo mostrou um painel vazio?
  4. Por que a chave é o pedidoId? Com 3 partições, quantos caixas trabalham, e o quarto?
  5. O que acontece entre gravar no H2 e publicar no Kafka, e qual é o nome da solução definitiva?

🏠 Em casa

Consolide com provas, não com capturas de tela

Em casa · até o próximo sábado

Traga provas e o seu quadro [22]

  1. Termine os atos que ficaram pela metade, inclusive o Ato 5 (ponto 18). Travou? Continue pelo ZIP do ato anterior, listado no ponto 09.
  2. Provoque a escrita dupla (ponto 19) e rode o Prompt 7. Guarde o diagnóstico do agente.
  3. Repetição de propósito: no painel do Kafka, Topics → pagamentos-resultados → Produce Message, publique de novo o resultado de um pedido já pago, com a mesma chave, trocando o status para RECUSADO. Preveja antes: o pedido muda na tela? E o painel do Analytics?
  4. Um terceiro leitor, sem tocar na API: peça ao agente um notificacoes-py, grupo notificacoes, que imprime “Olá Ana, seu pedido #12 foi pago!” para cada resultado aprovado. Conte quantas linhas da API você mudou.
  5. Escreva o seu quadro RabbitMQ × Kafka com suas palavras, em specs/quando-usar-qual.md: três critérios que você mais usaria e um exemplo da Loja Café para cada.
  6. Entregue o link do repositório (um commit por ato) e três frases: “O marcador do grupo fica… Rodar de novo zerou porque… O replay reconstruiu porque…”.
  7. Leia antes do próximo sábado o capítulo de OO de verdade e SOLID, a base do Cap 04.
Consulta rápida

Glossário de bolso [23]

Os termos do dia, na ordem em que aparecem.

Chamada síncrona
Quem chama fica parado esperando a resposta. Ex.: a API ligando para o Pagamentos por HTTP no Ato 2.
Acoplamento temporal
Duas peças precisam estar no ar ao mesmo tempo para a operação funcionar.
H2
Banco de dados que roda dentro do próprio programa Java, salvo num arquivo. Bom para aula e testes.
CORS
Regra do navegador: uma página de uma origem (localhost:5173) só chama outra (localhost:8080) se ela autorizar.
Polling
Perguntar de tempos em tempos se algo mudou. O front faz GET a cada 2 s.
Flask
Framework web mínimo de Python: um arquivo, uma função por rota.
.venv
Ambiente virtual do Python: as bibliotecas de um projeto, isoladas do resto do computador.
Kafka
Plataforma de eventos que guarda mensagens como um log em disco, para vários leitores.
Log
Sequência de registros em que só se acrescenta no fim (append-only).
Retenção
Por quanto tempo (ou até quanto espaço) o Kafka guarda os registros. Padrão: 7 dias.
Registro
Uma mensagem no Kafka: chave, valor e horário, numa posição (offset).
Offset
A posição de um registro dentro de uma partição. Começa em 0 e nunca muda.
Tópico
Um log com nome, dedicado a um assunto. Ex.: pedidos-criados.
Broker
O servidor do Kafka, que guarda os tópicos.
Produtor
Programa que escreve registros num tópico.
Consumidor
Programa que lê registros de um tópico.
Grupo de consumidores
Nome com que um ou mais consumidores leem juntos, com um marcador por partição.
Commit do offset
O consumidor avisa o broker até onde o grupo já processou.
Lag
Quantos registros o grupo ainda não leu: fim da partição menos o marcador.
Replay
Voltar o marcador de um grupo e ler de novo.
auto-offset-reset
Onde começa um grupo sem marcador: earliest (início) ou latest (fim).
Partição
Pedaço de um tópico, com ordem e offsets próprios.
Chave
Texto do registro usado para escolher a partição. Mesma chave, mesma partição.
Hash
Conta que transforma um texto num número; sempre dá o mesmo resultado para o mesmo texto.
murmur2
O hash que o cliente Java usa para escolher a partição. No Python, ative com partitioner: murmur2_random.
Rebalanceamento
O grupo redistribui as partições quando um consumidor entra ou sai.
KRaft
Modo em que o Kafka se organiza sozinho, sem Zookeeper.
Advertised listener
Endereço que o broker informa aos clientes para se conectarem.
acks=all
O produtor só considera gravado quando o broker (e suas réplicas) confirmou. É o padrão nos clientes atuais.
Evento
Mensagem que conta um fato que já aconteceu. Nome no passado: PedidoPago.
Idempotente
Aplicar duas vezes dá o mesmo resultado que aplicar uma.
Escrita dupla
Gravar no banco e publicar no broker em operações separadas, sem transação única.
Outbox
Padrão que grava o evento numa tabela na mesma transação e publica depois.
KafkaTemplate
Classe do Spring para publicar num tópico: send(tópico, chave, valor).
@KafkaListener
Anotação do Spring que transforma um método em consumidor de um tópico.
Visão derivada
Dado calculado a partir do log; pode ser descartado e reconstruído.