Pular para o conteúdo principal

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ícioExplicaçãoExemplo Prático
Custo Zero quando ParadoSó paga quando há requisiçõesRestaurant fechado = R$ 0,00
Escalabilidade AutomáticaAWS gerencia tráfego automaticamenteBlack Friday = sem travamentos
Sem Servidor para GerenciarFoco no código, não na infraestruturaEquipe pequena = mais produtividade
Alta DisponibilidadeAWS garante 99.95% de uptimeRestaurant 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

TecnologiaPara que ServePor que Escolhemos
Node.js 20Executar o código JavaScriptRápido e tem muitas bibliotecas
TypeScriptAdicionar tipos ao JavaScriptPrevine bugs e facilita manutenção
ZodValidar dados de entradaGarante que dados estão corretos
PrismaConversar com o banco de dadosFacilita consultas complexas
JWT + CognitoAutenticação de usuáriosSeguro 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

TipoExemploQuem Acessa
Pública/menu/restaurante-abcClientes do restaurant
Privada/admin/produtosFuncionários autenticados
Sistema/healthMonitoramento 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 ErroO que AconteceuCódigo HTTPExemplo
Dados InválidosCliente enviou info errada422Preço negativo
Não EncontradoItem não existe404Produto inexistente
Não AutorizadoUsuário sem permissão401Token expirado
ConflitoAção não permitida409Produto já existe
Erro do SistemaProblema técnico500Banco 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égiaO que FazResultado
Bundle PequenoCódigo compactadoInicia mais rápido
Lazy LoadingCarrega apenas o necessárioMenos memória
Connection PoolingReutiliza conexões do bancoMenos espera
Cache InteligenteGuarda dados frequentesResposta 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:

  1. ✅ Definir contratos - O que entra e o que sai
  2. ✅ Validar entrada - Garantir dados corretos
  3. ✅ Implementar lógica - Processar a requisição
  4. ✅ Tratar erros - Respostas claras para problemas
  5. ✅ Testar segurança - Verificar permissões por restaurante
  6. ✅ Adicionar logs - Para debug futuro
  7. ✅ Escrever testes - Garantir funcionamento
  8. ✅ Configurar alertas - Monitorar em produção
  9. ✅ Documentar - Explicar como usar
  10. ✅ 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.