Coders Club · Aula técnica

Você clicou em Comprar.
E agora?

Do Backend ao Data Warehouse: construindo sistemas orientados a eventos e dados

What Happens After You Click Buy?

COMPRAR
Backend Data Engineering Distributed Systems Architecture

Alex Seixas · Senior Software Engineer · CTO

02

A pergunta

O que acontece tecnicamente depois que você clica em Comprar?

COMPRAR
Pergunta para a turma
Descreva o caminho — do clique até o dado existir em algum lugar.
Resposta esperada
O frontend dispara uma requisição para algum backend. Regras de negócio são executadas, dados são persistidos e outras operações podem ser iniciadas.
03

Primeira arquitetura

Browser
POST /orders
Backend API
PostgreSQL
POST /orders

{
  "productId": "prod_123",
  "quantity": 1
}

Um clique. Um POST. Um banco.

04

O backend

const order = await createOrder(input);

return {
  id: order.id,
  status: "PENDING"
};
Request
Regras
Persist
Response

O cliente espera só o caminho crítico do request.

05

O pedido não pode existir pela metade

A
Atomicity
Tudo ou nada.
C
Consistency
Constraints locais valem no commit.
I
Isolation
Concorrência sem atropelo.
D
Durability
Commit sobrevive a crash.

ACID é o contrato do banco — não de todo o sistema distribuído.

06

Uma transação

BEGIN;

INSERT INTO orders (...);
INSERT INTO order_items (...);

COMMIT;
Pergunta para a turma
E se o segundo INSERT falhar?
Resposta esperada
Rollback. Nenhum pedido órfão. Atomicidade mantida — o banco desfaz o primeiro INSERT.
07

One service became five

Order Service
Payment
Inventory
Fraud
Notification
Analytics

Cobrar. Reservar. Checar. Avisar. Medir.

Novas responsabilidades. Mesmo request. Novo risco.

08

Implementação ingênua

await createOrder();
await chargePayment();
await reserveInventory();
await runFraudCheck();
await sendEmail();
await trackAnalytics();
Pergunta para a turma
Qual é o problema?
Discuta
Latência soma. Falha em email cancela compra. Um serviço lento segura o checkout. Acoplamento temporal, de disponibilidade e de deploy.
09

Latency adds up

DB
40ms
Payment
450ms
Inventory
90ms
Fraud
380ms
Email
250ms
Analytics
120ms

TOTAL > 1s  ·  o usuário espera tudo

Cada await no caminho crítico é tempo na cara do usuário.

10

Acoplamento

Backend
↓ direto a tudo
Pay
Inventory
Fraud
Email ✕
Analytics
Temporal
Espera cada chamada terminar.
Availability
A queda de um vira a queda de todos.
Deployment
Mudar email exige mexer no checkout.

Se não queremos esperar tudo, o que muda no modelo do request?

Pergunta para a turma
Se o serviço de email estiver fora, devemos impedir uma compra?
Resposta esperada
Provavelmente não. Email é importante. Checkout é crítico. Isolar falhas é o ponto.
11

Sync vs Async

Sync

Request
Operation
Wait
Response

Async

Request
Persist
Queue / Event
Response
SyncAsync
Latencysoma tudosó o caminho crítico
Failure isolationfracamelhor
Complexitybaixamais estados, retries, observabilidade
Consistencyimediata no requesteventual entre serviços

Async não apaga o trabalho. Muda quando ele acontece.

12

O pedido tem estados

PENDING PAYMENT_PROCESSING PAID
alternativas PAYMENT_FAILED CANCELLED

Sem transação global, o pedido vira uma máquina de estados.

13

O time de dados chegou

Everything was fine.
Until Marketing arrived.

“A gente precisa de um dashboard.”

Pergunta para a turma
Podemos conectar o BI diretamente no banco de produção?
Resposta esperada
Quase nunca. Queries analíticas competem com o checkout por CPU, I/O e locks. OLTP não foi desenhado para scan de ano.
14

Uma query inocente

SELECT
  DATE(created_at),
  product_id,
  SUM(total)
FROM orders
WHERE created_at >= NOW() - INTERVAL '1 year'
GROUP BY 1, 2;

500M rows scanned.

O checkout acabou de competir com o dashboard.

15

OLTP vs OLAP

OLTP

Operação

transações curtas · baixa latência · INSERT/UPDATE · concorrência

PostgreSQLMySQL

OLAP

Analytics

scans grandes · agregações · columnar · throughput de leitura

SnowflakeBigQueryRedshift

Mesmos dados. Workloads completamente diferentes.

16

Read replica

PostgreSQL Primary
↓ replicação
Read Replica
BI
Pergunta para a turma
Problema resolvido?
Resposta esperada
Parcialmente. Isola leituras do primary. Ainda é o mesmo modelo operacional — sem histórico analítico, sem transformações, sem fontes além do Postgres.
17

ETL / ELT

ETL

Extract
Transform
Load

Transforma antes de entrar no destino.

ELT

Extract
Load
Transform

Carrega cru. Transforma no warehouse — dbt, SQL, escala do destino.

A ordem de Transform e Load muda quem paga a complexidade.

18

O pipeline aparece

PostgreSQL
Stripe
CRM
Logs
Raw Data
Warehouse
Transformation
Analytics
19

Batch vs Streaming

Batch

Processar 1× por hora

Simples. Barato. Atraso aceitável para muitos dashboards.

Streaming

À medida que acontece

Menor atraso. Mais peças móveis. Operação mais cara.

Real-time sobe complexity, cost e operational burden.

Se o Order Service não deve conhecer cada sistema, como distribuímos a informação?

Pergunta para a turma
Real-time sempre é melhor?
Resposta esperada
Não. Real-time sobe complexity, cost e operational burden. Use quando a decisão de negócio precisa do agora — não por padrão.
20

OrderCreated happened. Who needs to know?

Order Service
OrderCreated
Event Bus
Pay
Inventory
Fraud
Notification

Producer doesn't need to know every consumer.

21

O evento

{
  "eventId": "evt_01J...",
  "type": "OrderCreated",
  "version": 1,
  "occurredAt": "2026-09-01T18:00:00Z",
  "data": {
    "orderId": "ord_987",
    "customerId": "usr_42",
    "total": 249.90
  }
}
eventId type version occurredAt payload

Um evento é um fato versionado — não um dump da tabela.

22

Kafka — o log compartilhado

Producer
topic: orders
Partition 0offset 0..n
Partition 1offset 0..n
Partition 2offset 0..n
↓ ↓ ↓
Consumers
TopicPartitionOffset ProducerConsumerConsumer Group

Kafka não é uma fila tradicional. É um log distribuído.

23

Ordem no Kafka

Ordem existe — mas o escopo importa.

Partition 0evt A → B → C
Partition 1evt X → Y
Partition 2evt M → N
partition key = orderId
Pergunta para a turma
Se OrderCreated e OrderPaid forem para partitions diferentes, o que pode acontecer?
Resposta esperada
Ordem entre eles deixa de ser garantida. Por isso a key = orderId coloca o ciclo do pedido na mesma partition.
24

Cada grupo lê o mesmo tópico

topic orders
↓ fan-out
Payment Group
offset próprio
Fraud Group
offset próprio
Analytics Group
offset próprio

Um grupo escala partições. Grupos diferentes não roubam mensagens uns dos outros.

Mesmo tópico. Offsets independentes por consumer group.

25

Kafka vs SQS

Kafka

Event streaming · Replay · Log
Multiple consumer groups · High throughput

Vários times leem o mesmo histórico. Analytics entra depois e ainda alcança o passado.

SQS

Queue · Worker processing
Jobs assíncronos · Retry / DLQ · AWS managed

Uma mensagem, um worker. Simples para “faça este trabalho”. Sem replay nativo de log.

There is no universal winner.

SQS não é “Kafka simples”. São modelos diferentes.

26

Delivery semantics

At-most-once

Pode perder.
Nunca duplica.

AT-LEAST-ONCE

Não perde.
Pode duplicar.

Exactly-once

Caro. Condicional.
Não é mágica.

At-least-once + idempotency. Esse é o combo prático.

Retries resolvem falhas transitórias. E criam um problema novo.

27

O crash no pior momento

OrderCreated
chargeCard()
Payment processed
CRASH — offset não salvo
Mensagem de novo
Pergunta para a turma
O cliente paga duas vezes?
Depende
Se chargeCard() não for idempotente — sim. At-least-once sem idempotência é cobrança duplicada.
28

Idempotency

Idempotency-Key: order_987

// gateway de pagamento
// mesma chave = mesma cobrança
processed_events
  event_id     PK
  processed_at

// se event_id existe → skip

Chave de idempotência no provedor. Ou tabela local de eventos processados. Ou os dois.

Retrying is easy. Retrying safely is hard.

29

Failures happen. What happens next?

Event
agora
1s
2s
4s
8s

Backoff protege o destino. Não é só cortesia.

1s → 2s → 4s → 8s

30

Dead Letter Queue

Retry exhausted
Dead Letter Queue

Poison message: nunca vai passar. Retry infinito é um loop de CPU e custo.

Conseguimos entregar eventos. Como garantir que o evento exista sempre que o pedido existir?

Pergunta para a turma
Por que não retry infinito?
Resposta esperada
Mensagem inválida não cura com tempo. DLQ isola o veneno, alerta humanos e deixa o resto do tópico fluir.
31

Database committed. Kafka didn't.

await db.orders.insert(order);

await kafka.publish({
  type: "OrderCreated",
  orderId: order.id
});

DB COMMIT ✓

KAFKA PUBLISH ✕

Two writes. Two systems. No shared transaction.

Pergunta para a turma
Agora temos um pedido sem evento. E aí?
Dual-write problem
Dois writes, sem transação compartilhada. Pedido commitado sem evento — ou evento fantasma se o DB der rollback depois do publish.
32

Transactional Outbox

BEGIN;
  INSERT orders
  INSERT outbox_events
COMMIT;
PostgreSQL
orders
outbox_events
Outbox Publisher
Kafka

Persist the change and the intent to publish together.

33

A tabela outbox

idaggregate_idevent_typepayloadcreated_atpublished_at
1ord_987OrderCreated{…}18:00:0118:00:02
2ord_988OrderCreated{…}18:00:04NULL

Publisher lê unpublished, envia, marca published_at. Crash? Lê de novo.

Pergunta para a turma
Outbox garante exactly-once?
Resposta esperada
Não necessariamente. Publisher pode enviar e crashar antes de marcar. Consumers ainda precisam tolerar duplicatas.
34

CDC — Change Data Capture

PostgreSQL
↓ WAL
Debezium
Kafka
Polling Outbox
Simples. Lag de poll. Carga extra no banco.
CDC
Lê o log de transação. Menos poll. Mais infra. Não “resolve consistência” sozinho.

Observe changes instead of repeatedly asking for them.

Agora temos um histórico de eventos. Como viram informação para o negócio?

35

Agora os dados

Now we have events.
Data Engineering wants them.

Everything is an event.

OrderCreated
OrderPaid
OrderCancelled
Kafka
Data Platform
36

Medallion

Bronze

Raw events

Como chegou. Replay possível.

Silver

Clean / normalized

Tipos, chaves, dedup.

Gold

Business metrics

Pronto para o dashboard.

Common pattern, not a universal rule.

37

Data Lake e Warehouse

Data Lake
armazena cru, barato, muitos formatos
Warehouse
modelo analítico, SQL, governança
Analytics
Snowflake BigQuery Redshift

Operational models answer transactions. Analytical models answer questions.

Lake não substitui warehouse — são camadas com jobs diferentes.

38

Star schema

dim_customer
dim_date
fact_orders
dim_product
dim_location
Fact — evento / métrica de negócio
Dimensions — contexto para fatiar

Fact = métrica. Dimension = contexto.

39

dbt — transformações versionadas

raw.orders
stg_orders
fct_orders
mart_sales
SELECT
  order_id,
  customer_id,
  total
FROM {{ ref('stg_orders') }}
WHERE status = 'PAID'

Transformações versionadas. Lineage legível.

40

Data quality

order_id

unique
not_null

customer_id

relationship

total

>= 0

Data quality starts at the source.

Pergunta para a turma
Quem é responsável pela qualidade do dado?
Resposta esperada
Toda a cadeia. Aplicação → Events → Pipeline → Warehouse. Teste no dbt não conserta payload errado na origem.
41

Data contracts

// ANTES
{ "status": "paid" }
// DEPOIS
{ "status": "completed" }
Deploy ✓
Consumers 💥

Your event is an API.

Pergunta para a turma
O deploy passou. Está tudo certo?
Resposta esperada
Não. O contrato quebrou. CI verde no producer não prova que payment, fraud e dbt ainda entendem o evento.
42

Schema evolution

OrderCreated v1
OrderCreated v2
Backward
Consumidor novo lê evento antigo.
Forward
Consumidor antigo tolera evento novo.
Versioning
Campo version no envelope.
Registry
Schema Registry valida o contrato.

Compatibilidade é disciplina — não acidente.

A arquitetura funciona. Quando algo quebra, como descobrimos onde?

43

The architecture works. Can we debug it?

Um pedido desapareceu. Onde ele está?

HTTP  correlationId: abc123
OrderCreated  eventId: evt123
Payment
Inventory
Analytics
Logs Metrics Traces

If an order disappears, can we trace its journey?

44

A UI atrasou. É bug?

Order #987

PAYMENT_PROCESSING

alguns momentos depois

PAID

Atraso de convergência ≠ bug automático.

Pergunta para a turma
Isso é bug?
Resposta esperada
Não necessariamente. Eventual consistency: o sistema converge. UX precisa mostrar estado intermediário — não fingir atomicidade global.
45

What could possibly go wrong?

Clique um card · R revela o próximo

Kafka unavailable

O que acontece no checkout?

Outbox guarda o evento. Publisher tenta de novo. Compra pode completar. Consumidores atrasam — não deveriam falhar o POST.

Payment unavailable

O pedido some?

Pedido fica PAYMENT_PROCESSING. Retry + timeout → PAYMENT_FAILED. Não apagar o pedido.

Duplicate event

Cobra duas vezes?

Não, se idempotência existir. processed_events ou Idempotency-Key no gateway.

Out-of-order event

Paid antes de Created?

Partition por orderId reduz isso. State machine ignora transições inválidas. Não assumir ordem global.

Consumer offline

As mensagens morrem?

Kafka retém. Group retoma do offset. Lag cresce. Alerta de consumer lag.

Warehouse offline

O checkout para?

Não. Caminho operacional isolado. Pipeline acumula. Batch/stream alcança depois.

Breaking schema

CI verde resolve?

Consumidores quebram ou vão pra DLQ. Contract tests e registry deveriam ter barrado o deploy.

Replay de uma semana

Pode religar analytics?

Com log (Kafka) e consumidores idempotentes, sim. Com fila que apaga mensagem, o passado já foi.

46

A arquitetura que construímos

Client
clique único
API
request/response rápido
Orders + Outbox
uma transação
CDC
sem dual-write
Kafka
fan-out + replay
Pay
idempotente
Inventory
reserva async
Fraud
não bloqueia UX
Notify
falha isolada
Pipeline
eventos viram dados
Lake
raw
Warehouse
OLAP
dbt
métricas
Analytics
decisão
Observability atravessa tudo · correlationId / eventId / orderId

Cada caixa existe por um problema que já vimos.

47

Agora é com vocês

Marketplace · 5M usuários · 500k pedidos/dia

Checkout · Payment · Inventory · Fraud · Notifications · Near-real-time analytics

Qual banco?
Sync ou async?
Kafka ou SQS?
Como garantir idempotência?
Como evitar dual-write?
Como Data recebe os eventos?
Como fazer replay?
Como evoluir schemas?
Como monitorar orderId ponta a ponta?
48

Uma possível solução

PostgreSQL no checkout. Warehouse à parte.
Sync só criar pedido + outbox. Resto async.
Kafka para fan-out e replay. SQS ok para workers simples de email.
Idempotency-Key = orderId + processed_events.
Outbox na mesma TX. CDC se o time opera Debezium.
Consumer group analytics → lake → warehouse → dbt.
Version + registry. Campos novos opcionais.
correlationId no HTTP, evento e traces.

Trade-offs: Kafka pede operação. CDC pede WAL expertise. Near-real-time analytics custa mais que batch de 5 minutos — 500k/dia talvez não precise de stream em tudo.

49

How this appears in interviews

Clique ou R — uma de cada vez.

How would you design an order processing system?

Fale em estágios
Comece sync+DB. Introduza eventos pelo problema de latência e acoplamento. Outbox. Consumidores. Dados.

What is idempotency?

Definição
Aplicar o mesmo comando N vezes tem o mesmo efeito que uma. Essencial com at-least-once.

How would you solve the dual-write problem?

Padrão
Transactional outbox ou CDC a partir do commit. Não dois commits independentes.

Kafka vs SQS?

Modelo
Log + replay + vários grupos vs fila de trabalho. Contexto decide. Não existe vencedor universal.

Same event processed twice?

Efeito
Sem idempotência: cobrança dupla. Com chave/tabela: no-op na segunda vez.

OLTP vs OLAP?

Workload
Transações curtas vs scans e agregações. Motores e modelos diferentes.

Operational data → analytics?

Caminho
Eventos ou CDC → lake/warehouse → transform (dbt) → métricas. Não BI no primary.

How do you evolve event schemas?

Contrato
Compatibilidade, versionamento, registry. Breaking change é incidente, não detalhe.
COMPRAR
Client
API
PG+Outbox
Kafka
Pay
Inv
Fraud
Mail
Lake
WH
dbt

One click.

Dozens of architectural decisions.

Software generates data.
Data tells us what the software did.
Architecture connects both.

Backend Data Distributed Systems Architecture

Obrigado.

Perguntas?

Space  ·   ·  R resposta  ·  N notes  ·  P presenter  ·  Esc