Serverless na AWS: Quando Vale a Pena e Quando é Cilada

Minha experiência real com Lambda, API Gateway e DynamoDB. Onde serverless brilha, onde complica, e quanto realmente custa em produção.

AWSServerlessLambdaAPI GatewayDynamoDB

Todo mundo fala que serverless é o futuro. Depois de usar em produção por um bom tempo, posso dizer: depende. Tem cenário que é perfeito, tem cenário que vira pesadelo.

Onde Serverless Brilha

Vou direto ao ponto - serverless faz sentido quando:

  • Tráfego imprevisível - Escala de 0 a milhares sem você fazer nada
  • Workloads esporádicos - Paga só quando executa
  • Eventos e integrações - Reagir a upload S3, mensagem SQS, etc
  • APIs simples - CRUD sem muita lógica complexa

Já usei pra processar webhooks de pagamento. Tráfego zero a maior parte do tempo, picos quando tinha campanha. Custo total: menos de $5/mês.

Onde Vira Dor de Cabeça

Por outro lado, evito serverless quando:

  • Processamento longo - Lambda tem limite de 15 minutos
  • Conexões persistentes - WebSocket funciona, mas é gambiarrento
  • Tráfego alto e constante - EC2/Fargate fica mais barato
  • Debug complexo - Rastrear problema em 50 Lambdas encadeados não é divertido

Como Estruturo um Projeto Serverless

Uso o Serverless Framework porque simplifica muito o deploy. A estrutura que funciona pra mim:

project/
├── src/
│   ├── handlers/          # Cada Lambda é um arquivo
│   │   ├── createItem.ts
│   │   ├── getItem.ts
│   │   └── listItems.ts
│   ├── services/          # Lógica de negócio
│   └── utils/             # Helpers
├── serverless.yml
└── package.json

O serverless.yml básico:

service: minha-api

provider:
  name: aws
  runtime: nodejs20.x
  region: sa-east-1

  environment:
    TABLE_NAME: ${self:service}-${sls:stage}

functions:
  createItem:
    handler: src/handlers/createItem.handler
    events:
      - http:
          path: /items
          method: post
          cors: true

  getItem:
    handler: src/handlers/getItem.handler
    events:
      - http:
          path: /items/{id}
          method: get
          cors: true

O Padrão de Handler que Uso

Depois de muito refatorar, cheguei nessa estrutura que deixa o código testável:

// src/handlers/createItem.ts
import { APIGatewayProxyHandler } from 'aws-lambda';
import { ItemService } from '../services/ItemService';

const itemService = new ItemService();

export const handler: APIGatewayProxyHandler = async (event) => {
  try {
    const body = JSON.parse(event.body || '{}');

    // Validação simples - não precisa de lib pesada
    if (!body.name) {
      return {
        statusCode: 400,
        body: JSON.stringify({ error: 'name é obrigatório' }),
      };
    }

    const item = await itemService.create(body);

    return {
      statusCode: 201,
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(item),
    };
  } catch (error) {
    console.error('Erro ao criar item:', error);

    return {
      statusCode: 500,
      body: JSON.stringify({ error: 'Erro interno' }),
    };
  }
};

A lógica fica no service, separada do handler. Assim consigo testar sem precisar mockar o evento inteiro do API Gateway.

DynamoDB - Single Table Design

DynamoDB é diferente de SQL. No começo eu criava uma tabela por entidade, como faria no MySQL. Erro.

O lance é usar Single Table Design - tudo na mesma tabela, diferenciando pelo padrão da chave:

PK              | SK              | Dados
----------------|-----------------|------------------
USER#123        | PROFILE         | nome, email, ...
USER#123        | ORDER#456       | total, status, ...
USER#123        | ORDER#789       | total, status, ...
ORDER#456       | METADATA        | userId, items, ...

Parece estranho no início, mas a vantagem é buscar tudo de um usuário com uma única query:

// Busca usuário + todos os pedidos dele
const result = await docClient.query({
  TableName: 'minha-tabela',
  KeyConditionExpression: 'PK = :pk',
  ExpressionAttributeValues: {
    ':pk': `USER#${userId}`,
  },
});

Cold Start - O Elefante na Sala

Cold start é quando Lambda precisa inicializar do zero. Pode adicionar 100ms a 1s+ na resposta.

O que faço pra minimizar:

  1. Manter as Lambdas pequenas - Menos código = inicialização mais rápida
  2. Usar Node.js ou Python - Java e .NET têm cold start maior
  3. Provisioned Concurrency pra endpoints críticos:
functions:
  checkout:
    handler: src/handlers/checkout.handler
    provisionedConcurrency: 5  # Mantém 5 instâncias "quentes"

Provisioned Concurrency custa mais, então uso só onde latência importa (checkout, login).

Monitoramento que Funciona

Não adianta ter tudo rodando se você não sabe quando quebra. O mínimo:

# CloudWatch Alarm pra erros
resources:
  Resources:
    ErrorAlarm:
      Type: AWS::CloudWatch::Alarm
      Properties:
        AlarmName: ${self:service}-errors
        MetricName: Errors
        Namespace: AWS/Lambda
        Statistic: Sum
        Period: 300
        EvaluationPeriods: 1
        Threshold: 5
        ComparisonOperator: GreaterThanThreshold

Também ativo X-Ray pra rastrear requests:

provider:
  tracing:
    lambda: true
    apiGateway: true

Custos Reais

Vou ser específico porque “serverless é barato” é muito vago.

Pra uma API com 1 milhão de requests/mês:

RecursoUsoCusto
API Gateway1M requests~$3.50
Lambda1M invocações, 200ms média~$0.40
DynamoDB1M writes, 5M reads~$2.50
CloudWatchLogs básicos~$5.00
Total~$11/mês

Compara com um EC2 t3.small rodando 24/7: ~$15/mês. Mas serverless escala automaticamente e você não gerencia servidor.

Agora, se você tem 100M requests/mês constantes, a conta muda. Aí EC2 ou Fargate provavelmente sai mais barato.

Quando Migrei de Volta pra Container

Tive um caso onde comecei serverless e migrei pra ECS Fargate depois.

O motivo: a aplicação precisava de conexão persistente com banco PostgreSQL. Lambda abre/fecha conexão a cada invocação. Com tráfego alto, o banco não aguentava tantas conexões.

Tinha a opção de usar RDS Proxy, mas adicionava mais um componente (e custo). No fim, Fargate com connection pooling resolveu melhor.

Minha Stack Serverless Atual

Quando decido ir de serverless, essa é a stack:

  • API Gateway - REST ou HTTP (HTTP é mais barato)
  • Lambda - Node.js 20
  • DynamoDB - On-demand billing
  • SQS - Pra processamento assíncrono
  • EventBridge - Pra eventos entre serviços
  • Step Functions - Pra workflows complexos

Funciona bem, custo baixo, e não preciso me preocupar com servidor.


Quer discutir se serverless faz sentido pro seu caso? Me chama no LinkedIn.