Voltar para o Blog
Anúncio

Arquitetura Serverless em 2025: Por Que Sua Próxima API Deveria Ser Serverless

Olá HaWkers, você ainda mantém servidores rodando 24/7 esperando requisições? Paga por capacidade que não usa 90% do tempo? Passa madrugadas configurando auto-scaling?

Serverless mudou o jogo. Em 2025, a questão não é mais "devo usar serverless?", mas sim "por que ainda não estou usando?". Vou te mostrar exatamente como funciona, quando usar, e código real de produção.

O Que É Serverless (De Verdade)?

Serverless NÃO significa "sem servidores". Significa que você não gerencia servidores.

Modelo tradicional:

  • Você provisiona EC2/VPS
  • Instala Node.js, PM2, nginx
  • Configura load balancer, auto-scaling
  • Monitora uso de CPU/RAM
  • Paga 24/7, mesmo sem tráfego

Modelo serverless:

  • Você escreve função
  • Deploy com um comando
  • Plataforma gerencia tudo
  • Escala automaticamente (0 a milhões de requisições)
  • Paga apenas por execução real
Anúncio

Principais Plataformas em 2025

1. AWS Lambda (O Gigante)

  • Maior ecossistema (integração com 200+ serviços AWS)
  • Suporta Node.js, Python, Go, Java, .NET, Rust
  • Cold start: ~100-200ms (melhorou muito)
  • Pricing: $0.20 por 1M requisições + compute time

2. Vercel Functions (O Dev-Friendly)

  • Deploy integrado com Git
  • Edge network global
  • Perfeito para Next.js/React
  • Cold start: ~50ms
  • Pricing: 100k invocações grátis, depois $0.40/1M

3. Cloudflare Workers (O Mais Rápido)

  • Edge computing (roda em 300+ cidades)
  • Cold start: ~0ms (sempre "quente")
  • V8 isolates (não containers)
  • Pricing: 100k/dia grátis, depois $0.50/1M

4. Netlify Functions (O Simples)

  • Integração perfeita com JAMstack
  • Deploy automático
  • Good for startups
  • Pricing: 125k/mês grátis
Anúncio

Sua Primeira Function: Hello World Real

AWS Lambda + API Gateway:

// handler.js
exports.handler = async (event) => {
  const { name = 'World' } = event.queryStringParameters || {};

  return {
    statusCode: 200,
    headers: {
      'Content-Type': 'application/json',
      'Access-Control-Allow-Origin': '*'
    },
    body: JSON.stringify({
      message: `Hello, ${name}!`,
      timestamp: new Date().toISOString(),
      requestId: event.requestContext.requestId
    })
  };
};

Deploy com Serverless Framework:

# serverless.yml
service: hello-world-api

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

functions:
  hello:
    handler: handler.handler
    events:
      - httpApi:
          path: /hello
          method: get

# Deploy com um comando
# serverless deploy

Resultado: API rodando em https://xxxxxxx.execute-api.us-east-1.amazonaws.com/hello?name=Jeff

Serverless Computing

Caso Real: API de E-commerce

Vamos construir API completa de produtos com CRUD, validação e cache.

// api/products/index.js
import { DynamoDBClient } from '@aws-sdk/client-dynamodb';
import { DynamoDBDocumentClient, ScanCommand } from '@aws-sdk/lib-dynamodb';

const client = new DynamoDBClient({});
const docClient = DynamoDBDocumentClient.from(client);

export const handler = async (event) => {
  const { httpMethod, pathParameters, body } = event;

  try {
    switch (httpMethod) {
      case 'GET':
        return pathParameters?.id
          ? await getProduct(pathParameters.id)
          : await listProducts();

      case 'POST':
        return await createProduct(JSON.parse(body));

      case 'PUT':
        return await updateProduct(pathParameters.id, JSON.parse(body));

      case 'DELETE':
        return await deleteProduct(pathParameters.id);

      default:
        return response(405, { error: 'Method not allowed' });
    }
  } catch (error) {
    console.error('Error:', error);
    return response(500, { error: error.message });
  }
};

// Helper: Resposta padronizada
function response(statusCode, data) {
  return {
    statusCode,
    headers: {
      'Content-Type': 'application/json',
      'Access-Control-Allow-Origin': '*'
    },
    body: JSON.stringify(data)
  };
}

// GET /products
async function listProducts() {
  const command = new ScanCommand({
    TableName: process.env.PRODUCTS_TABLE
  });

  const result = await docClient.send(command);

  return response(200, {
    products: result.Items,
    count: result.Count
  });
}

// GET /products/:id
async function getProduct(id) {
  const { GetCommand } = await import('@aws-sdk/lib-dynamodb');

  const command = new GetCommand({
    TableName: process.env.PRODUCTS_TABLE,
    Key: { id }
  });

  const result = await docClient.send(command);

  if (!result.Item) {
    return response(404, { error: 'Product not found' });
  }

  return response(200, result.Item);
}

// POST /products
async function createProduct(data) {
  const { PutCommand } = await import('@aws-sdk/lib-dynamodb');

  // Validação
  const errors = validateProduct(data);
  if (errors.length > 0) {
    return response(400, { errors });
  }

  const product = {
    id: crypto.randomUUID(),
    ...data,
    createdAt: new Date().toISOString(),
    updatedAt: new Date().toISOString()
  };

  const command = new PutCommand({
    TableName: process.env.PRODUCTS_TABLE,
    Item: product
  });

  await docClient.send(command);

  return response(201, product);
}

// PUT /products/:id
async function updateProduct(id, data) {
  const { UpdateCommand } = await import('@aws-sdk/lib-dynamodb');

  const errors = validateProduct(data, true);
  if (errors.length > 0) {
    return response(400, { errors });
  }

  // Construir expression de update dinamicamente
  const updateExpressions = [];
  const expressionAttributeNames = {};
  const expressionAttributeValues = {};

  Object.keys(data).forEach((key, index) => {
    updateExpressions.push(`#field${index} = :value${index}`);
    expressionAttributeNames[`#field${index}`] = key;
    expressionAttributeValues[`:value${index}`] = data[key];
  });

  updateExpressions.push('#updatedAt = :updatedAt');
  expressionAttributeNames['#updatedAt'] = 'updatedAt';
  expressionAttributeValues[':updatedAt'] = new Date().toISOString();

  const command = new UpdateCommand({
    TableName: process.env.PRODUCTS_TABLE,
    Key: { id },
    UpdateExpression: `SET ${updateExpressions.join(', ')}`,
    ExpressionAttributeNames: expressionAttributeNames,
    ExpressionAttributeValues: expressionAttributeValues,
    ReturnValues: 'ALL_NEW'
  });

  const result = await docClient.send(command);

  return response(200, result.Attributes);
}

// DELETE /products/:id
async function deleteProduct(id) {
  const { DeleteCommand } = await import('@aws-sdk/lib-dynamodb');

  const command = new DeleteCommand({
    TableName: process.env.PRODUCTS_TABLE,
    Key: { id }
  });

  await docClient.send(command);

  return response(204, null);
}

// Validação de produto
function validateProduct(data, isUpdate = false) {
  const errors = [];

  if (!isUpdate && !data.name) {
    errors.push('Name is required');
  }

  if (data.name && data.name.length < 3) {
    errors.push('Name must be at least 3 characters');
  }

  if (!isUpdate && data.price === undefined) {
    errors.push('Price is required');
  }

  if (data.price !== undefined && (data.price < 0 || isNaN(data.price))) {
    errors.push('Price must be a positive number');
  }

  return errors;
}
Anúncio

Otimizações Essenciais

1. Reduzir Cold Starts:

// ❌ Ruim: Import dentro da função
export const handler = async () => {
  const AWS = require('aws-sdk'); // Import a cada execução
  const db = new AWS.DynamoDB.DocumentClient();
  // ...
};

// ✅ Bom: Import no topo (reutilizado)
const { DynamoDBDocumentClient } = require('@aws-sdk/lib-dynamodb');
const docClient = DynamoDBDocumentClient.from(new DynamoDBClient({}));

export const handler = async () => {
  // docClient já está instanciado
};

// ✅ Ainda melhor: Lazy loading
let docClient;

function getDocClient() {
  if (!docClient) {
    const { DynamoDBClient } = require('@aws-sdk/client-dynamodb');
    const { DynamoDBDocumentClient } = require('@aws-sdk/lib-dynamodb');
    docClient = DynamoDBDocumentClient.from(new DynamoDBClient({}));
  }
  return docClient;
}

2. Provisioned Concurrency (Para APIs críticas):

# serverless.yml
functions:
  api:
    handler: handler.handler
    provisionedConcurrency: 2 # Sempre 2 instâncias "quentes"
    reservedConcurrency: 100 # Limita concorrência máxima

3. Caching Inteligente:

// Cache em memória (persiste entre invocações da mesma instância)
let cachedProducts = null;
let cacheExpiry = 0;

async function getProductsCached() {
  const now = Date.now();

  if (cachedProducts && now < cacheExpiry) {
    console.log('Cache hit!');
    return cachedProducts;
  }

  console.log('Cache miss, fetching...');
  const products = await fetchProductsFromDB();

  cachedProducts = products;
  cacheExpiry = now + (5 * 60 * 1000); // Cache por 5 minutos

  return products;
}

4. Bundle Size Otimizado:

// ❌ Ruim: Import completo
const _ = require('lodash');

// ✅ Bom: Import específico
const debounce = require('lodash/debounce');

// ✅ Ainda melhor: Tree-shaking com ES modules
import { debounce } from 'lodash-es';
Anúncio

Integrações Poderosas

1. S3 Triggers (Processar uploads):

// Redimensionar imagens automaticamente
export const handler = async (event) => {
  const s3 = new S3Client({});
  const sharp = require('sharp');

  for (const record of event.Records) {
    const bucket = record.s3.bucket.name;
    const key = record.s3.object.key;

    // Download da imagem
    const { Body } = await s3.send(new GetObjectCommand({
      Bucket: bucket,
      Key: key
    }));

    const buffer = await streamToBuffer(Body);

    // Redimensionar
    const resized = await sharp(buffer)
      .resize(800, 600, { fit: 'inside' })
      .jpeg({ quality: 80 })
      .toBuffer();

    // Upload da versão redimensionada
    await s3.send(new PutObjectCommand({
      Bucket: bucket,
      Key: `thumbnails/${key}`,
      Body: resized,
      ContentType: 'image/jpeg'
    }));

    console.log(`✓ Resized ${key}`);
  }
};

2. EventBridge Scheduled (Cron jobs):

functions:
  sendDailyReport:
    handler: handlers/reports.daily
    events:
      - schedule:
          rate: cron(0 9 * * ? *) # Todo dia às 9h UTC
          enabled: true
// handlers/reports.js
export const daily = async () => {
  const users = await fetchActiveUsers();
  const report = generateReport(users);

  await sendEmail({
    to: 'admin@example.com',
    subject: 'Daily Report',
    body: report
  });

  console.log(`✓ Sent report to ${users.length} users`);
};

3. SQS Queues (Processamento assíncrono):

// Producer: Envia mensagens para fila
export const createOrder = async (event) => {
  const sqs = new SQSClient({});
  const order = JSON.parse(event.body);

  await sqs.send(new SendMessageCommand({
    QueueUrl: process.env.ORDERS_QUEUE_URL,
    MessageBody: JSON.stringify(order)
  }));

  return response(202, { message: 'Order queued for processing' });
};

// Consumer: Processa mensagens da fila
export const processOrder = async (event) => {
  for (const record of event.Records) {
    const order = JSON.parse(record.body);

    try {
      await processPayment(order);
      await sendConfirmationEmail(order);
      await updateInventory(order);

      console.log(`✓ Order ${order.id} processed`);
    } catch (error) {
      console.error(`✗ Order ${order.id} failed:`, error);
      // Mensagem volta para fila (retry automático)
      throw error;
    }
  }
};

Custos Reais: Comparação

Cenário: API com 1M requisições/mês, 200ms médio

SoluçãoCusto/mêsDetalhes
EC2 t3.small (24/7)$17+ $5 Load Balancer = $22
AWS Lambda$0.40$0.20 req + $0.20 compute
Vercel Functions$0.40Após 100k grátis
Cloudflare Workers$5Plano pago ($5/mês)

Vantagem serverless: Economiza 80-90% em tráfego baixo/médio.

Quando serverless fica caro:

  • Tráfego constante alto (>10M req/mês)
  • Funções de longa duração (>15min)
Anúncio

Quando NÃO Usar Serverless

Evite para:

  1. WebSockets persistentes (use EC2/ECS)
  2. Processamento pesado (ML training, video encoding)
  3. Latência crítica (<10ms consistente)
  4. Estado persistente em memória (caches grandes)

Alternativa híbrida:

// API serverless + Worker tradicional
// Lambda para endpoints
export const api = async (event) => {
  // Processa requisição leve
  // Para tarefas pesadas, envia para fila
  await sqs.sendMessage({
    queueUrl: WORKER_QUEUE,
    body: JSON.stringify({ task: 'heavy-compute' })
  });
};

// EC2 worker processa fila
// Roda 24/7, otimizado para tarefas pesadas

O Futuro do Serverless

Tendências 2025:

  • Edge computing (Cloudflare Workers, Vercel Edge)
  • Serverless containers (AWS Fargate, Cloud Run)
  • Streaming responses (dados progressivos)
  • IA integrada (embeddings, summarization)

Para entender melhor padrões assíncronos essenciais em serverless, confira Descobrindo o Poder do Async/Await em JavaScript.

Bora pra cima! 🦅

💻 Domine JavaScript de Verdade

O conhecimento que você adquiriu neste artigo é só o começo. Há técnicas, padrões e práticas que transformam desenvolvedores iniciantes em profissionais requisitados.

Invista no Seu Futuro

Preparei um material completo para você dominar JavaScript:

Formas de pagamento:

  • 3x de R$34,54 sem juros
  • ou R$97,90 à vista

📖 Ver Conteúdo Completo

Anúncio
Post anteriorPróximo post

Comentários (0)

Esse artigo ainda não possui comentários 😢. Seja o primeiro! 🚀🦅

Adicionar comentário