Arquitetura Lambda API
O que é a Lambda API?
A Lambda API é a porta de entrada da nossa aplicação. Imagine ela como um porteiro inteligente de um prédio:
- Recebe visitantes (requisições HTTP dos clientes)
- Verifica credenciais (autentica via JWT tokens)
- Valida documentos (valida dados de entrada)
- Direciona para o apartamento certo (chama o caso de uso correto)
- Entrega a resposta (formata dados para o cliente)
Importante: A Lambda API não toma decisões de negócio - apenas organiza e direciona. As regras de negócio ficam na camada de aplicação.
Fluxo Simplificado
Cliente → API Gateway → Lambda → Lógica de Negócio → Banco de Dados
↓ ↓ ↓ ↓ ↓
Requisição Auth Validação Processamento Resposta
Por que Usamos Lambda?
Vantagens para o BITIO
| Benefício | Explicação | Exemplo Prático |
|---|---|---|
| Custo Zero quando Parado | Só paga quando há requisições | Restaurant fechado = R$ 0,00 |
| Escalabilidade Automática | AWS gerencia tráfego automaticamente | Black Friday = sem travamentos |
| Sem Servidor para Gerenciar | Foco no código, não na infraestrutura | Equipe pequena = mais produtividade |
| Alta Disponibilidade | AWS garante 99.95% de uptime | Restaurant sempre online |
Como Funciona na Prática
Usuário abre cardápio
↓
API Gateway recebe requisição
↓
Valida se usuário pode acessar (JWT)
↓
Lambda processa a solicitação
↓
Busca dados no banco
↓
Retorna cardápio formatado
Tecnologias Utilizadas
| Tecnologia | Para que Serve | Por que Escolhemos |
|---|---|---|
| Node.js 20 | Executar o código JavaScript | Rápido e tem muitas bibliotecas |
| TypeScript | Adicionar tipos ao JavaScript | Previne bugs e facilita manutenção |
| Zod | Validar dados de entrada | Garante que dados estão corretos |
| Prisma | Conversar com o banco de dados | Facilita consultas complexas |
| JWT + Cognito | Autenticação de usuários | Seguro e gerenciado pela AWS |
Exemplo Prático: Criando um Produto no Menu
Vamos ver como funciona quando um restaurante adiciona um prato novo:
// 1. Cliente envia dados do produto
const novoPrato = {
nome: "Hambúrguer Especial",
preco: 25.90,
descricao: "Hambúrguer artesanal com queijo...",
categoria: "lanches"
};
// 2. Lambda valida os dados
const dadosValidados = validarProduto(novoPrato);
// 3. Chama a lógica de negócio
const produto = await criarProduto(dadosValidados);
// 4. Retorna sucesso para o cliente
return {
sucesso: true,
produto: produto,
mensagem: "Produto criado com sucesso!"
};
Como Organizamos o Código
Imagine a Lambda como uma empresa bem organizada com departamentos específicos:
Estrutura por Responsabilidade
┌─────────────────────────────────────────┐
│ Handler (Recepção) │ ← Recebe clientes, organiza documentos
│ - Extrai informações da requisição │
│ - Valida se tudo está correto │
│ - Chama o departamento certo │
│ - Formata a resposta final │
├─────────────────────────────────────────┤
│ Use Case (Gerência) │ ← Toma decisões de negócio
├─────────────────────────────────────────┤
│ Database/Storage (Arquivo) │ ← Guarda e busca informações
└─────────────────────────────────────────┘
Exemplo Real: Handler de Produto
// Essa é nossa "recepcionista" para criar produtos
export const criarProdutoHandler = async (requisicao) => {
try {
// 1. Pegar informações do usuário logado
const usuario = pegarUsuarioLogado(requisicao);
// 2. Validar se os dados estão corretos
const dadosProduto = validarDadosProduto(requisicao.body);
// 3. Chamar a "gerência" para criar o produto
const produto = await criarProdutoUseCase.executar({
restauranteId: usuario.restauranteId,
...dadosProduto,
});
// 4. Retornar sucesso
return respostaSucesso(201, produto);
} catch (erro) {
return respostaErro(erro);
}
};
Esta organização garante que cada parte do código tem uma responsabilidade específica e clara.
Segurança: Quem Pode Fazer O Quê?
Sistema de Autenticação (Como o Bouncer de uma Balada)
sequenceDiagram
participant C as Cliente/App
participant AG as Porteiro (API Gateway)
participant L as Sistema (Lambda)
participant CO as Segurança (Cognito)
C->>AG: "Quero entrar" + carteirinha (JWT)
AG->>CO: "Este documento é válido?"
CO-->>AG: "Sim, ele é do Restaurant XYZ"
AG->>L: "Cliente autorizado do Restaurant XYZ"
L-->>AG: Dados do cardápio
AG-->>C: "Aqui está seu cardápio!"
Na Prática: Multi-Tenant (Vários Restaurantes)
Cada restaurante só vê seus próprios dados:
// ✅ CORRETO: Sempre verificar o restaurante do usuário
function pegarUsuarioLogado(requisicao): InfoUsuario {
const token = requisicao.headers.authorization;
return {
usuarioId: token.sub,
restauranteId: token['custom:restaurante_id'], // ← CHAVE DA SEGURANÇA
papel: token['custom:role'], // admin, funcionario, etc
email: token.email,
};
}
// ✅ CORRETO: Sempre filtrar por restaurante
async function buscarProdutos(restauranteId: string) {
return await banco.produto.buscarTodos({
where: { restauranteId: restauranteId } // ← Nunca esquecer!
});
}
// ❌ ERRADO: Buscar todos os produtos (vazou dados)
async function buscarTodosProdutos() {
return await banco.produto.buscarTodos(); // ← PERIGO!
}
Rotas Públicas vs Privadas
| Tipo | Exemplo | Quem Acessa |
|---|---|---|
| Pública | /menu/restaurante-abc | Clientes do restaurant |
| Privada | /admin/produtos | Funcionários autenticados |
| Sistema | /health | Monitoramento AWS |
Validação: Garantindo Dados Corretos
Por que Validar?
Imagine um restaurante onde o garçom anota pedidos sem verificar se fazem sentido:
- "Quero 1 hambúrguer por R$ -10,00" ❌
- "Pizza de chocolate com -5 fatias" ❌
- "Delivery para endereço: 'asdfgh'" ❌
A validação é como ter um garçom experiente que sempre pergunta:
- O preço é positivo? ✅
- O nome do produto existe? ✅
- O endereço faz sentido? ✅
Como Funciona na Prática
// Definimos as "regras do jogo" primeiro
const regrasParaProduto = {
nome: "obrigatório, entre 1 e 255 caracteres",
preco: "obrigatório, deve ser positivo",
descricao: "opcional, até 1000 caracteres",
categoria: "obrigatório, deve existir no sistema"
};
// Depois checamos se os dados seguem as regras
function validarProduto(dados) {
const resultado = regrasParaProduto.validar(dados);
if (!resultado.valido) {
throw new ErroValidacao("Dados inválidos", resultado.erros);
}
return resultado.dadosLimpos;
}
Exemplo de Validação com Feedback Claro
// ❌ Dados enviados incorretos
{
"nome": "",
"preco": -5.99,
"categoria": "categoria-inexistente"
}
// ✅ Resposta explicativa para correção
{
"erro": "Dados inválidos",
"detalhes": [
{
"campo": "nome",
"problema": "Nome é obrigatório"
},
{
"campo": "preco",
"problema": "Preço deve ser maior que zero"
},
{
"campo": "categoria",
"problema": "Categoria não existe"
}
]
}
Tratamento de Erros: Quando Algo Dá Errado
Tipos de Problemas e Como Tratamos
| Tipo de Erro | O que Aconteceu | Código HTTP | Exemplo |
|---|---|---|---|
| Dados Inválidos | Cliente enviou info errada | 422 | Preço negativo |
| Não Encontrado | Item não existe | 404 | Produto inexistente |
| Não Autorizado | Usuário sem permissão | 401 | Token expirado |
| Conflito | Ação não permitida | 409 | Produto já existe |
| Erro do Sistema | Problema técnico | 500 | Banco fora do ar |
Mapeamento Automático de Erros
// Sistema inteligente que "traduz" erros técnicos em respostas amigáveis
function tratarErro(erro: Error) {
// Erros conhecidos do sistema
const mapeamento = {
'ProdutoNaoEncontrado': { codigo: 404, mensagem: 'Produto não encontrado' },
'DadosInvalidos': { codigo: 422, mensagem: 'Verifique os dados enviados' },
'UsuarioNaoAutorizado': { codigo: 401, mensagem: 'Faça login novamente' },
'ProdutoJaExiste': { codigo: 409, mensagem: 'Produto já cadastrado' },
};
const tipoErro = mapeamento[erro.constructor.name];
if (tipoErro) {
return respostaErro(tipoErro.codigo, tipoErro.mensagem);
}
// Erro inesperado = resposta genérica (não vazar detalhes técnicos)
registrarLog('Erro inesperado', erro);
return respostaErro(500, 'Erro interno do sistema');
}
Formato de Resposta
Sucesso:
{
"data": {
"id": "uuid-123",
"nome": "Hambúrguer Especial",
"preco": 29.90
},
"meta": {
"timestamp": "2024-07-02T10:30:00Z",
"versao": "v1"
}
}
Erro:
{
"erro": {
"codigo": "VALIDATION_ERROR",
"mensagem": "Os dados enviados contêm erros",
"detalhes": [
{
"campo": "preco",
"codigo": "DEVE_SER_POSITIVO",
"mensagem": "O preço deve ser maior que zero"
}
]
},
"timestamp": "2024-07-02T10:30:00Z"
}
Performance: Mantendo Tudo Rápido
Por que a Performance Importa?
- Cliente impaciente: Cardápio lento = cliente desiste
- Custo AWS: Lambda mais rápida = menos cobrança
- Experiência: App responsivo = mais pedidos
Como Otimizamos
| Estratégia | O que Faz | Resultado |
|---|---|---|
| Bundle Pequeno | Código compactado | Inicia mais rápido |
| Lazy Loading | Carrega apenas o necessário | Menos memória |
| Connection Pooling | Reutiliza conexões do banco | Menos espera |
| Cache Inteligente | Guarda dados frequentes | Resposta instantânea |
Exemplo: Otimização de Bundle
// ❌ Importa biblioteca inteira (pesado)
import * as AWS from 'aws-sdk';
// ✅ Importa apenas o que precisa (leve)
import { DynamoDB } from 'aws-sdk/clients/dynamodb';
Monitoramento: Sabendo Como Está a Saúde
O que Acompanhamos
// Registramos informações úteis para debug
logger.info('Processando pedido', {
restauranteId: 'rest-123',
produtoId: 'prod-456',
usuarioId: 'user-789',
tempo: '2024-07-02T10:30:00Z'
});
Alertas Automáticos
- Muitos erros (> 1%) → Notifica equipe técnica
- Resposta lenta (> 3 segundos) → Investiga performance
- Sistema fora → Ativa plano de contingência
Resumo: Checklist para Novo Endpoint
Quando criar uma nova funcionalidade na API:
- ✅ Definir contratos - O que entra e o que sai
- ✅ Validar entrada - Garantir dados corretos
- ✅ Implementar lógica - Processar a requisição
- ✅ Tratar erros - Respostas claras para problemas
- ✅ Testar segurança - Verificar permissões por restaurante
- ✅ Adicionar logs - Para debug futuro
- ✅ Escrever testes - Garantir funcionamento
- ✅ Configurar alertas - Monitorar em produção
- ✅ Documentar - Explicar como usar
- ✅ Testar performance - Verificar velocidade
Exemplo Prático Completo
// Endpoint: POST /produtos
export const criarProdutoHandler = async (event) => {
try {
// 1. Autenticação
const usuario = getAuthContext(event);
// 2. Validação
const dados = validarProduto(JSON.parse(event.body));
// 3. Lógica de negócio
const produto = await criarProdutoUseCase.execute({
restauranteId: usuario.restauranteId,
nome: dados.nome,
preco: dados.preco,
categoria: dados.categoria
});
// 4. Log de sucesso
logger.info('Produto criado', {
produtoId: produto.id,
restauranteId: usuario.restauranteId
});
// 5. Resposta
return respostaSucesso(201, {
id: produto.id,
nome: produto.nome,
preco: produto.preco,
mensagem: 'Produto criado com sucesso!'
});
} catch (erro) {
return tratarErro(erro);
}
};
A Lambda API é a base sólida que permite ao BITIO crescer com segurança, performance e facilidade de manutenção.