Este projeto demonstra um fluxo de pagamentos idempotente com concorrência segura.
Stack principal:
- backend em Node.js, TypeScript e Express
- PostgreSQL como fonte de verdade
- frontend em React, Vite, Tailwind CSS e componentes no padrão shadcn/ui
- frontend hospedado na Vercel
- backend e PostgreSQL hospedados no Render
- Neon como opção para Postgres em nuvem
O objetivo central é garantir que múltiplas tentativas com a mesma Idempotency-Key retornem exatamente a mesma resposta persistida, sem processamento duplicado.
Para uma visão executiva ainda mais curta, consulte docs/SUMMARY.md. Para testar a API via Postman, importe docs/postman_collection.json.
Instale as dependências:
pnpm installSuba banco, backend e frontend com um único comando:
pnpm dev:fullEsse fluxo:
- sobe o PostgreSQL local no Docker
- aguarda o banco ficar pronto
- aplica as migrations
- inicia backend e frontend em paralelo
Atalhos úteis:
pnpm dev
pnpm backend
pnpm frontend
pnpm db:setup
pnpm db:down
pnpm db:logs
pnpm lintEndereços locais:
- frontend:
http://localhost:5173 - backend:
http://localhost:3000 - Swagger UI:
http://localhost:3000/docs - Postman Collection:
docs/postman_collection.json
Health check:
curl http://localhost:3000/healthSe o objetivo for validar o projeto com o menor esforço possível, este é o roteiro mais direto:
- rodar
pnpm dev:full - abrir
http://localhost:5173para testar a interface - abrir
http://localhost:3000/docspara conferir o contrato da API - rodar
pnpm test:integrationpara validar a prova forte de idempotência e concorrência
Os pontos principais a observar são:
- a mesma
Idempotency-Keysempre reaproveita o mesmo resultado persistido - requests concorrentes com a mesma chave não duplicam processamento
- sucesso e falha são persistidos e reaproveitados
200representaSUCCESSpersistido,503representaFAILEDpersistido e202representaPENDINGtransitório
O deploy principal recomendado para este teste continua sendo Render para backend e Postgres, mas Neon pode ser usado sem mudar a lógica da aplicação.
Exemplo de configuração:
DATABASE_URL=postgresql://user:password@ep-xxxx.us-east-2.aws.neon.tech/dbname?sslmode=require
FRONTEND_URL=https://seu-frontend.vercel.app
PORT=3000
NODE_ENV=productionCuidados com Neon:
- Neon já usa pooling no lado deles com PgBouncer
- evite adicionar uma camada extra de pooling agressivo na aplicação
- para este projeto, a configuração simples com
pgé suficiente - em cenários que exigem semântica de sessão, prefira avaliar conexão direta em vez de “double pooling”
Criar um pagamento:
curl -i -X POST http://localhost:3000/payments \
-H 'Content-Type: application/json' \
-H 'Idempotency-Key: payment-001' \
-d '{"amount":100,"customerId":"customer-1"}'Repetir a mesma request com a mesma chave:
curl -i -X POST http://localhost:3000/payments \
-H 'Content-Type: application/json' \
-H 'Idempotency-Key: payment-001' \
-d '{"amount":100,"customerId":"customer-1"}'Simular concorrência local:
(
curl -s -X POST http://localhost:3000/payments \
-H 'Content-Type: application/json' \
-H 'Idempotency-Key: payment-concurrent-1' \
-d '{"amount":100,"customerId":"customer-1"}'
) & (
curl -s -X POST http://localhost:3000/payments \
-H 'Content-Type: application/json' \
-H 'Idempotency-Key: payment-concurrent-1' \
-d '{"amount":100,"customerId":"customer-1"}'
)
waitContrato esperado:
200paraSUCCESSpersistido503paraFAILEDpersistido202quando a chave já existe, o registro ainda está emPENDINGe o polling curto não encontrou estado final
Unitários:
pnpm test:unitIntegração com PostgreSQL real:
pnpm test:integrationOs testes de integração:
- usam PostgreSQL real via
TEST_DATABASE_URL - verificam estado persistido no banco
- provam que apenas uma request vence o processamento
- cobrem retry após sucesso, retry após falha e comportamento durante
PENDING
Verificação estática:
pnpm lintEstratégia enxuta recomendada:
- Vercel para
frontend/ - Render Web Service para
backend/ - Render PostgreSQL para
DATABASE_URL
Configuração mínima:
- Vercel
- Root Directory:
frontend - Build Command:
pnpm build
- Root Directory:
- Render
- Root Directory:
backend - Build Command:
pnpm install --frozen-lockfile && pnpm build - Start Command:
pnpm start - Health Check Path:
/health
- Root Directory:
Documentação da API:
GET /docs- Postman Collection:
docs/postman_collection.json
Decisões intencionais desta solução:
- PostgreSQL como fonte de verdade, não Redis
- sem lock distribuído explícito
- sem fila ou worker separado
- frontend simples, focado em demonstrar o comportamento do backend
Trade-offs aceitos:
- polling curto em vez de mecanismo mais sofisticado de notificação
503para falha persistida, mantendo o replay exato do status HTTP e do body final salvo- setup de deploy manual via painel, sem automação extra
- retenção de
Idempotency-Keypor janela configurável, por exemplo24h - rejeição explícita e auditável para payload divergente com mesma chave
- CI para build, lint e testes
- observabilidade mais forte em produção
- fila ou worker separado apenas se houver necessidade real de throughput ou integração externa