Problema
Operações transacionais precisam lidar com concorrência, respostas externas ambíguas e dependências que podem falhar. Este projeto explora essas propriedades em uma API de pagamentos, com código, testes e medições públicos; não representa um sistema financeiro em produção.
Arquitetura e decisões
O fluxo principal é HTTP → autenticação service-to-service → rate limiting → validação → claim de idempotência → resiliência do provider → persistência → resposta.
- PostgreSQL é a fonte de verdade. A
Idempotency-Keyé reivindicada atomicamente no banco. Os resultados distinguem claim novo, replay concluído, conflito de fingerprint e operação em processamento. A transação de banco não fica aberta durante a chamada externa ao provider. - Redis é degradável. É usado para cache-aside e rate limiting distribuído; não é fonte de verdade. Durante indisponibilidade, leituras continuam pelo PostgreSQL, com custo de timeout e menor proteção de rate limiting.
- O provider pode falhar. Há timeout, retry limitado, exponential backoff, jitter e circuit breaker, com classificação de falhas definitivas, transitórias e ambíguas. Repetir pode aumentar latência e custo; uma falha ambígua não prova que a operação externa deixou de acontecer.
- Operação observável. A API registra
X-Request-Id, logs JSON redigidos, métricas Prometheus em/metrics, liveness e readiness. O encerramento trata SIGTERM e drena requests em andamento.
O stack local usa PostgreSQL, Redis, migração e fake provider via Compose. A
imagem Docker é multi-stage, executa Bun como PID 1 e usuário não-root, com
filesystem read-only, capabilities removidas e no-new-privileges.
Benchmark local
Baseline executada em containers na mesma máquina Linux x86_64, com 4 vCPUs e aproximadamente 3,8 GiB de RAM; Bun 1.4.2, PostgreSQL 15.19, Redis 7.2.16, Docker e fake provider. Cada cenário principal teve três execuções de 300 requests, concorrência 25, após aquecimento. São medições locais, não uma previsão de throughput de produção ou AWS.
| Cenário | p50 | p95 | p99 | Throughput |
|---|---|---|---|---|
| GET cache hit | 3.065 ms | 4.818 ms | 5.833 ms | 7,524.7 req/s |
| GET cache miss | 7.301 ms | 10.210 ms | 11.706 ms | 3,289.3 req/s |
| GET replay persistente | 6.712 ms | 9.696 ms | 11.513 ms | 3,502.0 req/s |
| POST novo | 20.871 ms | 31.805 ms | 35.610 ms | 1,129.9 req/s |
Ensaios controlados complementares: provider com 100 ms artificiais teve p50
109.281 ms e p95 149.119 ms; retry 503 → success concluiu em 626.263 ms,
com um retry registrado. Com 20 POSTs simultâneos usando a mesma key, houve
uma criação, quatro replays concluídos e 15 respostas de operação em
processamento. Com Redis indisponível, os requests observados retornaram 200;
três runs tiveram p50 entre 192.934 ms e 753.890 ms, uma variação que impede
resumir o fallback em uma única latência típica. O teste de rate limit observou
429 com Retry-After ao exceder o limite configurado.
AWS validation lab temporário
Em us-east-1, Terraform provisionou 35 recursos para um lab temporário real.
A arquitetura exercitada foi HTTPS/ACM → ALB → ECS/Fargate → Bun API → RDS
PostgreSQL privado / ElastiCache Redis privado, com Secrets Manager,
CloudWatch Logs, ECR e IAM como serviços de apoio. A API e o fake provider
determinístico rodaram como containers da mesma task; uma task Fargate one-off
executou as migrations.
Para reduzir o custo do lab, as tasks ficaram em public subnets com public IP, mas aceitaram tráfego inbound somente do ALB. RDS e Redis permaneceram privados. Essa foi uma escolha temporária de laboratório, não a topologia preferida para produção: essa mantém as tasks ECS em private subnets com egress deliberado por NAT Gateway e/ou VPC endpoints.
| Verificação no lab AWS | Evidência observada |
|---|---|
Target do ALB e /health/ready |
healthy; HTTP 200 ready |
POST autenticado e replay com a mesma Idempotency-Key |
HTTP 201; depois HTTP 200 com a mesma transação |
| Cache-aside | cache_miss_total 1; cache_hit_total 1 |
| Rate limiter | 5 respostas HTTP 200; a 6ª recebeu HTTP 429 |
| CloudWatch Logs | Logs estruturados, request IDs e RATE_LIMIT_EXCEEDED |
| Serviço ECS ao final do smoke test | desired 1, running 1, pending 0, failed 0 |
O deploy também revelou dois ajustes necessários: o RDS recusou backup
retention de 7 dias no ambiente usado, então foi configurado para 1 dia; e o
PostgreSQL no RDS exigiu TLS, então a DATABASE_URL recebeu sslmode=require.
O ciclo foi fechado após os testes: ECR esvaziado, terraform destroy removeu
os 35 recursos e o Terraform state ficou vazio. A auditoria final não encontrou
ECS, RDS, ElastiCache, ALB, ECR ou CloudWatch Logs; o CNAME público foi removido.
O ALB e todos os demais recursos Terraform do lab foram destruídos. O
certificado público ACM foi mantido intencionalmente para possível reutilização;
os secrets ficaram agendados para exclusão.
Este foi um lab de validação temporário e não representa um ambiente de
produção. O serviço usou desired=1: não foi demonstrada alta disponibilidade nem
compartilhamento do rate limiter entre múltiplas réplicas.
Validação local e limites
O relatório de validação containerizada local registra 164 testes, 662 assertions, zero falhas — incluindo 20 integrações PostgreSQL e 11 Redis — e um shutdown real via SIGTERM durante request: a request terminou, o processo saiu sem SIGKILL e o encerramento levou cerca de 2,31 s.
- O benchmark é local, específico ao hardware descrito, executado com fake provider e cliente/servidor no mesmo host; não representa produção, AWS ou múltiplas réplicas. O lab AWS não foi usado para medir benchmark.
- Não há garantia exactly-once. O resultado externo ainda depende de o provider respeitar a mesma chave de idempotência.
- Redis fail-open mantém parte do serviço disponível, mas reduz a proteção de rate limiting durante outage; timeouts podem elevar bastante a latência.
- O circuit breaker é local ao processo, não coordenado entre réplicas.
- O fake provider determinístico do lab não valida a integração com um provedor de pagamentos externo.
- O lab de uma task não demonstra alta disponibilidade, comportamento com múltiplas réplicas ou rate limiting compartilhado entre elas.
- A topologia temporária usou tasks em subnets públicas. Ela não valida a topologia production-preferred com tasks ECS privadas.
Evidências
- Repositório: resilient-transaction-api no GitHub
- Relatório do AWS lab: aws-lab-real.md
- Artifacts sanitizados do AWS lab: artifacts/aws-lab
- ADRs: decisões de arquitetura
- Validação containerizada: phase-7v-progress.md
- Relatório do benchmark: phase-8-baseline.md
- Artefatos JSONL do benchmark Docker local: artifacts/benchmarks