Voltar para o Blog

Arquitetura Serverless em 2025: Da Teoria à Prática com JavaScript

Olá HaWkers, o mercado de serverless está projetado para atingir $17.78 bilhões em 2025. Mas o que isso significa para você, desenvolvedor JavaScript que quer construir aplicações escaláveis sem gerenciar servidores?

Neste artigo, vamos além do hype e exploramos quando usar serverless, como implementar corretamente e armadilhas comuns que podem custar caro se ignoradas.

O Que É Serverless Realmente?

Serverless não significa "sem servidores" — significa que você não gerencia servidores. A infraestrutura escala automaticamente e você paga apenas pelo que usa.

Principais Provedores em 2025

// Comparação de plataformas serverless populares
const serverlessPlatforms = {
  'AWS Lambda': {
    runtime: 'Node.js 20.x, Python, Go, Java, etc',
    coldStart: '~100-300ms (Node.js)',
    pricing: '$0.20 por 1M requests + compute',
    maxDuration: '15 minutes',
    bestFor: 'Backends complexos, integração AWS',
    limitations: 'Cold starts, complexidade inicial'
  },
  'Vercel Edge Functions': {
    runtime: 'Edge Runtime (V8 isolates)',
    coldStart: '~0ms (edge locations)',
    pricing: '$20/mês até 500k requests',
    maxDuration: '30 seconds (hobby), 5min (pro)',
    bestFor: 'APIs rápidas, middleware, SSR',
    limitations: 'Não suporta todas APIs Node.js'
  },
  'Cloudflare Workers': {
    runtime: 'V8 isolates',
    coldStart: '~0ms (distribuído globalmente)',
    pricing: '$5/mês até 10M requests',
    maxDuration: '50-300ms (depend do plano)',
    bestFor: 'Edge computing, baixa latência',
    limitations: 'Limite de CPU time, sem filesystem'
  },
  'Azure Functions': {
    runtime: 'Node.js, Python, C#, Java',
    coldStart: '~200-500ms',
    pricing: '$0.20 por 1M requests + compute',
    maxDuration: '10 minutes',
    bestFor: 'Integração com Microsoft stack',
    limitations: 'Cold starts similares a AWS'
  },
  'Google Cloud Functions': {
    runtime: 'Node.js, Python, Go, Java',
    coldStart: '~150-400ms',
    pricing: '$0.40 por 1M requests + compute',
    maxDuration: '9 minutes',
    bestFor: 'Integração com GCP, Firebase',
    limitations: 'Pricing ligeiramente mais alto'
  }
};

Quando Usar Serverless: Casos de Uso Ideais

1. APIs com Tráfego Variável

// Exemplo: API de e-commerce com picos sazonais
// AWS Lambda + API Gateway

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

  // Roteamento simples
  const routes = {
    'GET /products': getProducts,
    'GET /products/{id}': getProduct,
    'POST /orders': createOrder,
    'POST /checkout': processCheckout
  };

  const routeKey = `${httpMethod} ${path}`;
  const handlerFn = routes[routeKey];

  if (!handlerFn) {
    return {
      statusCode: 404,
      body: JSON.stringify({ error: 'Not found' })
    };
  }

  try {
    const result = await handlerFn(event);

    return {
      statusCode: 200,
      headers: {
        'Content-Type': 'application/json',
        'Access-Control-Allow-Origin': '*'
      },
      body: JSON.stringify(result)
    };
  } catch (error) {
    console.error('Handler error:', error);

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

async function getProducts(event) {
  // Cold start mitigation: Lazy load DB connection
  const db = await getDBConnection();

  const products = await db.query('SELECT * FROM products WHERE active = true');

  return {
    products,
    count: products.length,
    cached: false
  };
}

async function createOrder(event) {
  const orderData = JSON.parse(event.body);

  // Validação
  if (!orderData.customerId || !orderData.items) {
    throw new Error('Invalid order data');
  }

  const db = await getDBConnection();

  // Transação
  const order = await db.transaction(async (trx) => {
    const [orderId] = await trx('orders').insert({
      customer_id: orderData.customerId,
      total: orderData.total,
      status: 'pending'
    });

    await trx('order_items').insert(
      orderData.items.map(item => ({
        order_id: orderId,
        product_id: item.productId,
        quantity: item.quantity,
        price: item.price
      }))
    );

    return orderId;
  });

  // Trigger assíncrono para processamento
  await triggerOrderProcessing(order);

  return { orderId: order };
}

// Connection pooling para reduzir cold starts
let dbConnection = null;
async function getDBConnection() {
  if (!dbConnection) {
    const { Pool } = require('pg');
    dbConnection = new Pool({
      connectionString: process.env.DATABASE_URL,
      max: 1 // Lambda usa 1 conexão por instância
    });
  }
  return dbConnection;
}

2. Edge Functions para Performance Global

// Vercel Edge Function para personalização de conteúdo
// Roda em edge locations próximas ao usuário

export const config = {
  runtime: 'edge'
};

export default async function handler(request) {
  const { geo, nextUrl } = request;

  // Personalização baseada em localização
  const userCountry = geo.country || 'US';
  const userCity = geo.city || 'Unknown';

  // A/B testing no edge
  const variant = getABTestVariant(request);

  // Reescrever URL baseado em contexto
  if (userCountry === 'BR') {
    nextUrl.pathname = `/br${nextUrl.pathname}`;
  }

  // Adicionar headers customizados
  const response = await fetch(nextUrl, {
    headers: {
      'X-User-Country': userCountry,
      'X-User-City': userCity,
      'X-AB-Variant': variant
    }
  });

  // Modificar resposta antes de enviar ao usuário
  const html = await response.text();
  const modifiedHtml = html.replace(
    '<head>',
    `<head>
      <script>window.__USER_CONTEXT__ = ${JSON.stringify({ userCountry, userCity, variant })}</script>`
  );

  return new Response(modifiedHtml, {
    headers: {
      'Content-Type': 'text/html',
      'Cache-Control': 's-maxage=60, stale-while-revalidate'
    }
  });
}

function getABTestVariant(request) {
  const cookie = request.cookies.get('ab_test_variant');

  if (cookie) {
    return cookie.value;
  }

  // 50/50 split
  return Math.random() < 0.5 ? 'A' : 'B';
}

3. Background Jobs e Processamento Assíncrono

// AWS Lambda triggered por SQS para processamento de imagens
export const handler = async (event) => {
  // SQS pode enviar múltiplas mensagens em batch
  const records = event.Records;

  // Processar em paralelo (respeitando limites de memória/CPU)
  const results = await Promise.all(
    records.map(async (record) => {
      try {
        const message = JSON.parse(record.body);
        await processImage(message);

        return { success: true, messageId: record.messageId };
      } catch (error) {
        console.error('Failed to process message:', error);

        // Retornar para retry (DLQ após X tentativas)
        throw error;
      }
    })
  );

  return { processedCount: results.length };
};

async function processImage(message) {
  const { imageUrl, userId, transformations } = message;

  // 1. Download imagem do S3
  const imageBuffer = await downloadFromS3(imageUrl);

  // 2. Aplicar transformações (resize, watermark, etc)
  const sharp = require('sharp');
  let image = sharp(imageBuffer);

  for (const transform of transformations) {
    if (transform.type === 'resize') {
      image = image.resize(transform.width, transform.height);
    } else if (transform.type === 'watermark') {
      image = image.composite([{
        input: await downloadWatermark(),
        gravity: 'southeast'
      }]);
    }
  }

  const processedBuffer = await image.toBuffer();

  // 3. Upload resultado de volta ao S3
  const resultKey = `processed/${userId}/${Date.now()}.jpg`;
  await uploadToS3(resultKey, processedBuffer);

  // 4. Notificar usuário via SNS
  await notifyUser(userId, resultKey);

  console.log(`Image processed successfully: ${resultKey}`);
}

Padrões Arquiteturais Serverless

1. API Gateway + Lambda (Arquitetura Clássica)

// Estrutura de projeto serverless
const serverlessArchitecture = {
  'API Gateway': {
    purpose: 'Roteamento HTTP, rate limiting, auth',
    routes: [
      'GET /api/users → Lambda: getUsers',
      'POST /api/users → Lambda: createUser',
      'GET /api/orders → Lambda: getOrders'
    ]
  },
  'Lambda Functions': {
    deployment: 'Função por rota (micro) ou monolítica',
    layers: 'Compartilhar dependências via Lambda Layers'
  },
  'DynamoDB / RDS': {
    choice: 'DynamoDB para NoSQL, Aurora Serverless para SQL',
    pattern: 'Connection pooling essencial'
  },
  'S3': {
    purpose: 'Storage de assets (imagens, uploads)',
    trigger: 'Lambda pode ser triggered por S3 events'
  },
  'SQS / EventBridge': {
    purpose: 'Comunicação assíncrona entre serviços',
    pattern: 'Event-driven architecture'
  }
};

// Exemplo de monorepo serverless
const projectStructure = `
/my-serverless-api
  /functions
    /users
      - get.js
      - create.js
      - update.js
    /orders
      - get.js
      - create.js
  /layers
    /database
      - connection.js
    /utils
      - validation.js
  /infrastructure
    - serverless.yml (ou CDK)
  /tests
    - integration.test.js
`;

2. JAMstack com Serverless Backend

// Next.js App Router + Serverless Functions
// app/api/newsletter/route.ts

import { NextRequest, NextResponse } from 'next/server';

export async function POST(request: NextRequest) {
  try {
    const { email } = await request.json();

    // Validação
    if (!isValidEmail(email)) {
      return NextResponse.json(
        { error: 'Invalid email' },
        { status: 400 }
      );
    }

    // Adicionar ao newsletter (ex: Mailchimp, SendGrid)
    await addToNewsletter(email);

    // Trigger welcome email (assíncrono via queue)
    await queueWelcomeEmail(email);

    return NextResponse.json(
      { success: true, message: 'Subscribed successfully' },
      { status: 200 }
    );
  } catch (error) {
    console.error('Newsletter subscription error:', error);

    return NextResponse.json(
      { error: 'Failed to subscribe' },
      { status: 500 }
    );
  }
}

async function addToNewsletter(email: string) {
  // Exemplo com API externa
  const response = await fetch('https://api.mailchimp.com/3.0/lists/{listId}/members', {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${process.env.MAILCHIMP_API_KEY}`,
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      email_address: email,
      status: 'subscribed'
    })
  });

  if (!response.ok) {
    throw new Error('Failed to add to Mailchimp');
  }
}

function isValidEmail(email: string): boolean {
  return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
}

// app/newsletter-form.tsx (Client Component)
'use client';

import { useState } from 'react';

export function NewsletterForm() {
  const [email, setEmail] = useState('');
  const [status, setStatus] = useState<'idle' | 'loading' | 'success' | 'error'>('idle');

  const handleSubmit = async (e: React.FormEvent) => {
    e.preventDefault();
    setStatus('loading');

    try {
      const response = await fetch('/api/newsletter', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ email })
      });

      if (response.ok) {
        setStatus('success');
        setEmail('');
      } else {
        setStatus('error');
      }
    } catch (error) {
      setStatus('error');
    }
  };

  return (
    <form onSubmit={handleSubmit}>
      <input
        type="email"
        value={email}
        onChange={(e) => setEmail(e.target.value)}
        placeholder="your@email.com"
        required
      />
      <button type="submit" disabled={status === 'loading'}>
        {status === 'loading' ? 'Subscribing...' : 'Subscribe'}
      </button>
      {status === 'success' && <p>Successfully subscribed!</p>}
      {status === 'error' && <p>Failed to subscribe. Please try again.</p>}
    </form>
  );
}

Otimizações e Best Practices

1. Reduzir Cold Starts

// Técnicas para mitigar cold starts

// 1. Provisioned Concurrency (AWS Lambda)
const serverlessConfig = {
  functions: {
    api: {
      handler: 'src/api.handler',
      provisionedConcurrency: 5, // Mantém 5 instâncias warm
      reservedConcurrency: 100   // Limite máximo de instâncias
    }
  }
};

// 2. Lazy Loading de Dependências
// ❌ Ruim: Importar tudo no topo
import AWS from 'aws-sdk';
import sharp from 'sharp';
import { Pool } from 'pg';

// ✅ Bom: Importar apenas quando necessário
export const handler = async (event) => {
  if (event.action === 'image') {
    const sharp = require('sharp'); // Lazy load
    // processar imagem
  } else if (event.action === 'database') {
    const { Pool } = require('pg');
    // query database
  }
};

// 3. Connection Reuse (Global Scope)
let dbPool = null;

export const handler = async (event) => {
  // Reusar conexão entre invocações (warm starts)
  if (!dbPool) {
    const { Pool } = require('pg');
    dbPool = new Pool({
      connectionString: process.env.DATABASE_URL,
      max: 1
    });
  }

  const client = await dbPool.connect();
  try {
    const result = await client.query('SELECT * FROM users');
    return result.rows;
  } finally {
    client.release();
  }
};

// 4. Bundle Size Optimization
// Use esbuild ou Webpack com tree-shaking
const esbuildConfig = {
  entryPoints: ['src/handler.ts'],
  bundle: true,
  minify: true,
  sourcemap: false,
  target: 'node20',
  platform: 'node',
  external: ['aws-sdk'], // Já incluído no Lambda runtime
  outfile: 'dist/handler.js'
};

2. Gestão de Custos

// Monitoramento e otimização de custos serverless
class ServerlessCostOptimizer {
  async analyzeCosts() {
    // 1. Identificar funções caras
    const expensiveFunctions = await this.getTopCostFunctions();

    // 2. Analisar padrões de uso
    for (const fn of expensiveFunctions) {
      const metrics = await this.getFunctionMetrics(fn.name);

      console.log(`Function: ${fn.name}`);
      console.log(`  Monthly cost: $${fn.cost}`);
      console.log(`  Invocations: ${metrics.invocations}`);
      console.log(`  Avg duration: ${metrics.avgDuration}ms`);
      console.log(`  Avg memory: ${metrics.avgMemory}MB`);

      // 3. Sugestões de otimização
      const suggestions = this.generateSuggestions(metrics);
      console.log('  Suggestions:', suggestions);
    }
  }

  generateSuggestions(metrics) {
    const suggestions = [];

    // Memória muito alta mas pouco usada
    if (metrics.configuredMemory > metrics.avgMemory * 1.5) {
      suggestions.push(
        `Reduce memory from ${metrics.configuredMemory}MB to ${Math.ceil(metrics.avgMemory * 1.2)}MB`
      );
    }

    // Duração alta sugere otimização de código
    if (metrics.avgDuration > 3000) {
      suggestions.push('Consider code optimization or caching');
    }

    // Alto número de cold starts
    if (metrics.coldStartRate > 0.1) {
      suggestions.push('Consider provisioned concurrency for critical paths');
    }

    return suggestions;
  }

  async getTopCostFunctions() {
    // Integrar com AWS Cost Explorer API
    // Retornar funções ordenadas por custo
    return [
      { name: 'image-processor', cost: 245.50 },
      { name: 'api-gateway', cost: 189.30 },
      { name: 'data-sync', cost: 156.70 }
    ];
  }
}

// Práticas de economia
const costSavingTips = {
  'Caching agressivo': {
    where: 'CloudFront, API Gateway, Lambda response',
    savings: '30-60% em invocações'
  },
  'Batch processing': {
    where: 'Processar múltiplos itens por invocação',
    savings: '50-70% em custos'
  },
  'Memory tuning': {
    where: 'Ajustar memória ao necessário',
    savings: '20-40% em custos'
  },
  'Reserved capacity': {
    where: 'Funções com tráfego previsível',
    savings: '30-50% em custos'
  }
};

Quando NÃO Usar Serverless

// Casos onde serverless pode não ser ideal
const serverlessLimitations = {
  'Long-running tasks': {
    problem: 'Limite de 15min (Lambda) ou 30s (Edge)',
    alternative: 'ECS Fargate, Kubernetes Jobs'
  },
  'WebSockets persistentes': {
    problem: 'Difícil manter conexões abertas',
    alternative: 'EC2, ECS com connection pooling'
  },
  'Processamento intenso de CPU': {
    problem: 'Custo pode ser alto vs servidor dedicado',
    alternative: 'EC2 com instâncias otimizadas'
  },
  'Latência ultra-baixa crítica': {
    problem: 'Cold starts podem afetar',
    alternative: 'Containers sempre-on ou bare metal'
  },
  'Alto volume constante': {
    problem: 'Pode ser mais caro que servidor fixo',
    calculation: 'Break-even geralmente em 50-70% de utilização constante'
  }
};

Conclusão: Serverless em 2025

Serverless amadureceu. Não é mais hype — é ferramenta essencial para:

  • Startups: Reduz custo inicial e acelera time-to-market
  • Scale-ups: Escala automaticamente com crescimento
  • Empresas: Permite focar em features, não em infra

Se você quer entender melhor JavaScript assíncrono (essencial para serverless), recomendo que dê uma olhada em outro artigo: JavaScript Assíncrono: Dominando Promises e Async/Await onde você vai descobrir as bases para trabalhar com funções serverless eficientemente.

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:

  • R$9,90 (pagamento único)

📖 Ver Conteúdo Completo

Comentários (0)

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

Adicionar comentário