Skip to content

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

Sistema de Rastreamento de Pedidos

Node.js TypeScript Express Prisma PostgreSQL AWS Lambda AWS CDK Jest GitHub Actions

📚 Projeto de estudo. Foi feito para praticar e demonstrar uma arquitetura serverless real na AWS (para entrevistas de dev backend/fullstack), não para uso em produção de verdade. Algumas escolhas priorizam aprendizado e baixo custo em vez de robustez máxima — os pontos assim estão sinalizados ao longo do texto.

Simula um fluxo real de e-commerce: o cliente cria um pedido e recebe resposta na hora, enquanto o processamento acontece em segundo plano. Tudo 100% serverless na AWS, provisionado como código (CDK).


Índice


O que a aplicação faz

Um pedido tem uma API com duas rotas:

Método Rota O que faz
POST /pedidos Cria o pedido (status PENDING) e responde na hora
GET /pedidos/:id Consulta o status atual do pedido

O pedido passa por até quatro estados:

PENDING → PROCESSING → COMPLETED
                     → FAILED

A criação (POST) é rápida porque não espera o processamento: ela só grava o pedido e dispara um evento. Um outro componente processa depois, em segundo plano, e atualiza o status. O cliente consulta o resultado quando quiser (GET).

Como o fluxo funciona

Cliente
   │
   ▼
Express API  (Lambda + API Gateway)
   │  1. grava o pedido no Postgres (status PENDING)
   │  2. publica um evento no SNS
   ▼
SNS Topic (pedido-criado)
   │
   ▼
SQS Queue  (fila principal)
   │
   ▼
Lambda consumer
   │  3. processa o pedido
   │  4. atualiza o Postgres (COMPLETED ou FAILED)
   │
   └── falhou 3x? → DLQ (dead letter queue)

Stack

  • Runtime: Node.js 20 (mínimo 20.19), TypeScript em modo estrito
  • API: Express, empacotado para Lambda via serverless-http
  • Banco / ORM: PostgreSQL + Prisma 6
  • Mensageria: SNS (pub/sub) + SQS (fila) + DLQ
  • Infra como código: AWS CDK (TypeScript)
  • Testes: Jest, Supertest, aws-sdk-client-mock
  • CI/CD: GitHub Actions com autenticação via OIDC

Estrutura do projeto

.
├── api/                     # projeto Node da API (Express + Prisma)
│   ├── prisma/schema.prisma #   modelo do Pedido
│   ├── src/
│   │   ├── server.ts        #   app Express (uso local)
│   │   ├── lambda.ts        #   adaptador serverless-http (Lambda da API)
│   │   ├── routes/          #   POST /pedidos, GET /pedidos/:id
│   │   ├── services/        #   publicação no SNS
│   │   └── lib/             #   Prisma Client + resolução da DATABASE_URL
│   └── tests/               #   testes unitários e de integração
├── infra/                   # projeto Node do CDK (infraestrutura)
│   ├── bin/infra.ts         #   instancia os 3 stacks
│   ├── lib/                 #   network-stack, database-stack, app-stack
│   └── lambda/              #   handler da Lambda consumer (SQS)
├── package.json             # raiz: só p/ o bundling do CDK (esbuild + prisma)
├── docker-compose.yml       # Postgres local para desenvolvimento
└── .github/workflows/       # ci.yml (testes) e deploy.yml (deploy)

api/ e infra/ são dois projetos Node separados, cada um com seu package.json. Há ainda um terceiro package.json na raiz, usado só pelo empacotamento das Lambdas no deploy.


🖥️ Rodando localmente (passo a passo)

Aqui você roda só a API + o banco na sua máquina, sem precisar de conta AWS.

Pré-requisitos

  • Node.js 20.19+ (node --version)
  • Docker (para subir o Postgres local)

1. Suba o banco de dados

O docker-compose.yml já vem configurado com um Postgres pronto (usuário, senha e banco pedidos). Na raiz do projeto:

docker compose up -d
docker compose ps          # confira que o status está "healthy"

Porta ocupada? Se você já tiver um Postgres na porta 5432, o container não sobe. Mude a porta no docker-compose.yml (ex.: "5433:5432") e ajuste a porta no DATABASE_URL do passo seguinte.

2. Configure a variável de ambiente

Crie o arquivo api/.env (ele não é versionado):

echo 'DATABASE_URL="postgresql://pedidos:pedidos123@localhost:5432/pedidos_db"' > api/.env

3. Instale as dependências e crie as tabelas

cd api
npm install
npx prisma migrate dev     # cria a tabela "pedidos" no banco local

4. Suba a API

npm run dev                # sobe em http://localhost:3000

5. Teste

Com curl:

# cria um pedido
curl -X POST http://localhost:3000/pedidos \
  -H "Content-Type: application/json" \
  -d '{"cliente":"Ana","produto":"Café","quantidade":2}'

# consulta pelo id retornado acima
curl http://localhost:3000/pedidos/<id>

Ou no Postman:

Método URL Body (raw → JSON)
POST http://localhost:3000/pedidos {"cliente":"Ana","produto":"Café","quantidade":2}
GET http://localhost:3000/pedidos/<id> —

⚠️ Sobre o processamento local: rodando localmente, o pedido fica em PENDING. O que muda o status (COMPLETED/FAILED) é a Lambda consumer, que só existe na AWS — localmente não há SNS/SQS de verdade. Por isso, no modo local, a publicação no SNS é ignorada (a menos que você defina a variável SNS_TOPIC_ARN). Para ver o ciclo completo funcionando sozinho, use o modo produção.


🧪 Rodando os testes

cd api
npm test
  • Unitários: o serviço de publicação no SNS, isolado, com aws-sdk-client-mock
  • Integração: as rotas da API contra um Postgres real (o do docker compose)
  • Handler consumer: simula um evento de SQS e valida o processamento

Os testes de integração usam o banco local, então ele precisa estar de pé (docker compose up -d) e migrado (npx prisma migrate dev).


☁️ Rodando em produção na AWS (passo a passo)

Aqui você sobe a infraestrutura de verdade e o fluxo assíncrono roda sozinho, ponta a ponta.

⚠️ Isto custa dinheiro. O RDS e os VPC Endpoints cobram enquanto existirem (~US$ 1–2 por dia). Este é um projeto de estudo: suba, teste e destrua (veja o passo 6). Não deixe rodando.

Pré-requisitos

  • Conta AWS e AWS CLI v2 instalada e configurada (aws configure), com região us-east-1.
    • Confirme com: aws sts get-caller-identity
    • 🔒 Use um usuário IAM com as permissões necessárias — evite as chaves do usuário root.
  • Node.js 20.19+

1. Instale as dependências dos três projetos

O empacotamento das Lambdas precisa das dependências na raiz, em api/ e em infra/:

npm install                # raiz (esbuild + prisma, p/ o bundling)
npm --prefix api install
npm --prefix infra install
npx prisma generate --schema api/prisma/schema.prisma

2. Prepare a conta para o CDK (só na primeira vez)

cd infra
npx cdk bootstrap aws://<numero-da-sua-conta>/us-east-1

3. Suba a infraestrutura

npx cdk deploy --all --require-approval never

Isso cria, nesta ordem: VPC → RDS → SNS/SQS/DLQ + API Gateway + as duas Lambdas. Leva ~10–15 min (o RDS é a parte lenta). No fim, o CDK imprime os outputs, incluindo a ApiUrl (a URL da sua API) — anote.

4. Crie as tabelas no banco de produção (migration)

O RDS fica numa rede privada, sem acesso pela internet. Para rodar a migration uma vez, libere temporariamente o seu IP e depois revogue:

# descubra seu IP público
MEU_IP=$(curl -s https://checkip.amazonaws.com)

# descubra o Security Group do RDS e libere a porta 5432 para o seu IP
SG=$(aws ec2 describe-security-groups \
  --filters "Name=group-name,Values=*DbSg*" \
  --query 'SecurityGroups[0].GroupId' --output text)
aws ec2 authorize-security-group-ingress \
  --group-id "$SG" --protocol tcp --port 5432 --cidr "$MEU_IP/32"

# monte a DATABASE_URL a partir do secret gerado pelo CDK e rode a migration
SECRET_ARN=$(aws cloudformation describe-stacks --stack-name PedidosDatabaseStack \
  --query "Stacks[0].Outputs[?OutputKey=='DbSecretArn'].OutputValue" --output text)
export DATABASE_URL=$(aws secretsmanager get-secret-value --secret-id "$SECRET_ARN" \
  --query SecretString --output text | python3 -c \
  "import sys,json;s=json.load(sys.stdin);print(f\"postgresql://{s['username']}:{s['password']}@{s['host']}:{s['port']}/{s['dbname']}\")")
npx prisma migrate deploy --schema ../api/prisma/schema.prisma

# revogue o acesso (importante!)
aws ec2 revoke-security-group-ingress \
  --group-id "$SG" --protocol tcp --port 5432 --cidr "$MEU_IP/32"

🔒 Trade-off de estudo: liberar o próprio IP é o jeito mais simples de rodar a migration. Em produção de verdade, isso seria feito por um bastion host ou pelo AWS SSM Session Manager, sem nunca expor o banco.

5. Teste a API real

Use a ApiUrl do passo 3 no Postman (ou curl):

# cria um pedido
curl -X POST https://<ApiUrl>/pedidos \
  -H "Content-Type: application/json" \
  -d '{"cliente":"Ana","produto":"Café","quantidade":2}'

# consulte alguns segundos depois: o status já deve estar COMPLETED
curl https://<ApiUrl>/pedidos/<id>

Diferente do modo local, aqui o pedido vira COMPLETED sozinho, porque o SNS → SQS → Lambda consumer processa em segundo plano. Para ver a DLQ em ação, crie um pedido cujo produto contenha a palavra FALHA (ex.: "Café com FALHA"): ele vira FAILED e a mensagem vai para a fila de erros após as tentativas.

6. Destrua tudo quando terminar 🧹

cd infra
npx cdk destroy --all

Faça isso para não continuar pagando pelo RDS e pelos VPC Endpoints.


Por que cada peça existe

Resumo das decisões de arquitetura

Por que separar a criação do processamento? Para a API responder rápido, sem travar esperando um processamento que pode demorar. O cliente só precisa saber que o pedido foi aceito.

Por que SNS e SQS, e não só SQS? O SNS desacopla quem publica de quem consome. Hoje só há uma fila ouvindo o tópico, mas amanhã dá para inscrever outra (ex.: envio de e-mail) sem tocar no código que publica. É o padrão fan-out.

Por que RDS numa VPC, e não um banco público? Segurança: um banco não deveria ser acessível pela internet. Isolado na VPC, só quem está dentro da rede fala com ele.

Por que sem NAT Gateway? Custo. As Lambdas só precisam falar com SQS, SNS e Secrets Manager (serviços da AWS), então usamos VPC Endpoints — uma rota privada mais barata — em vez de um NAT Gateway.

Por que Secrets Manager, e não a senha numa variável de ambiente? Segurança: a senha fica criptografada e pode ser rotacionada sem reimplantar. As Lambdas montam a DATABASE_URL a partir do secret no início da execução.

Por que a DLQ? Nem toda falha é temporária. Sem DLQ, uma mensagem problemática tentaria ser processada para sempre, travando as demais. Após 3 tentativas, ela sai da fila principal e vai para a DLQ, onde pode ser investigada.

Por que reportBatchItemFailures? A Lambda processa mensagens em lote. Com essa opção, só a mensagem que de fato falhou volta para a fila — as que deram certo não são reprocessadas.

CI/CD

  • ci.yml: roda os testes em todo PR e push, com um Postgres de serviço.
  • deploy.yml: no merge para main, roda cdk deploy autenticando na AWS via OIDC (sem access keys fixas no repositório). Requer configurar uma IAM Role de OIDC e o secret AWS_DEPLOY_ROLE_ARN no repositório.

Possíveis evoluções

Deixadas de fora de propósito, por ser um projeto de estudo:

  • Idempotência explícita no processamento (hoje há só a checagem por status)
  • RDS Proxy para pooling de conexões em maior escala
  • Domínio próprio no API Gateway (Route 53 + ACM)
  • Observabilidade com X-Ray e métricas customizadas no CloudWatch

Projeto desenvolvido por Paulo Roberto como estudo para recolocação como desenvolvedor backend/fullstack. Mais em paulorb.dev.

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages