Voltar para o Blog

Arquitetura Serverless: Como Reduzir Custos e Escalar Infinitamente com JavaScript

Olá HaWkers, serverless deixou de ser tendência futurista para se tornar arquitetura padrão em 2025. Com o mercado SaaS ultrapassando $300 bilhões e gastos em cloud pública atingindo $723.4 bilhões, entender serverless não é mais opcional.

JavaScript, com sua natureza event-driven, é perfeitamente compatível com plataformas como AWS Lambda, Google Cloud Functions e Azure Functions. Mas como aproveitar isso na prática?

O que É Serverless e Por Que Importa

Serverless não significa "sem servidores" - significa que você não gerencia servidores. A infraestrutura é completamente abstraída:

// Modelo Tradicional - Você gerencia tudo
const traditionalModel = {
  infrastructure: 'Você provisiona servidores',
  scaling: 'Você configura auto-scaling',
  availability: 'Você garante uptime',
  costs: 'Paga 24/7, mesmo sem tráfego',
  maintenance: 'Patches, updates, segurança = sua responsabilidade'
};

// Modelo Serverless - Cloud gerencia tudo
const serverlessModel = {
  infrastructure: 'Provisionado automaticamente',
  scaling: 'Escala automaticamente (0 a milhões)',
  availability: 'SLA de 99.95%+ garantido',
  costs: 'Paga APENAS por execuções',
  maintenance: 'Gerenciado pelo provider'
};

// Economia real
const costComparison = {
  traditional: {
    server: 'EC2 t3.medium = ~$30/mês',
    usage: 'Rodando 24/7',
    traffic: '10.000 requests/mês',
    costPerRequest: '$30 / 10.000 = $0.003'
  },
  serverless: {
    lambda: '10.000 requests = $0.20',
    freeEcuções '1M execuções/mês free tier',
    costPerRequest: '$0.20 / 10.000 = $0.00002',
    savings: '99.3% economia vs tradicional'
  }
};

serverless scaling

AWS Lambda com Node.js: Implementação Prática

Função Lambda Básica

// handler.js - AWS Lambda Function
export const handler = async (event) => {
  try {
    // Parse do body (se for POST)
    const body = event.body ? JSON.parse(event.body) : {};

    // Lógica de negócio
    const result = await processData(body);

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

    return {
      statusCode: 500,
      body: JSON.stringify({
        success: false,
        error: error.message
      })
    };
  }
};

async function processData(data) {
  // Sua lógica aqui
  return {
    processed: true,
    timestamp: new Date().toISOString()
  };
}

API REST Completa com Lambda

// api/users.js - CRUD de usuários
import { DynamoDBClient } from '@aws-sdk/client-dynamodb';
import {
  DynamoDBDocumentClient,
  GetCommand,
  PutCommand,
  DeleteCommand,
  ScanCommand
} from '@aws-sdk/lib-dynamodb';

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

const TABLE_NAME = process.env.USERS_TABLE;

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

  try {
    switch (httpMethod) {
      case 'GET':
        return await getUser(pathParameters?.id);
      case 'POST':
        return await createUser(JSON.parse(body));
      case 'PUT':
        return await updateUser(pathParameters?.id, JSON.parse(body));
      case 'DELETE':
        return await deleteUser(pathParameters?.id);
      default:
        return response(405, { error: 'Method not allowed' });
    }
  } catch (error) {
    console.error(error);
    return response(500, { error: error.message });
  }
};

async function getUser(id) {
  if (!id) {
    // List all users
    const result = await dynamo.send(
      new ScanCommand({ TableName: TABLE_NAME })
    );
    return response(200, result.Items);
  }

  // Get specific user
  const result = await dynamo.send(
    new GetCommand({
      TableName: TABLE_NAME,
      Key: { id }
    })
  );

  return response(200, result.Item);
}

async function createUser(data) {
  const user = {
    id: crypto.randomUUID(),
    ...data,
    createdAt: new Date().toISOString()
  };

  await dynamo.send(
    new PutCommand({
      TableName: TABLE_NAME,
      Item: user
    })
  );

  return response(201, user);
}

async function updateUser(id, data) {
  const user = {
    id,
    ...data,
    updatedAt: new Date().toISOString()
  };

  await dynamo.send(
    new PutCommand({
      TableName: TABLE_NAME,
      Item: user
    })
  );

  return response(200, user);
}

async function deleteUser(id) {
  await dynamo.send(
    new DeleteCommand({
      TableName: TABLE_NAME,
      Key: { id }
    })
  );

  return response(204, {});
}

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

Serverless Framework: Deploy Simplificado

# serverless.yml
service: my-api

provider:
  name: aws
  runtime: nodejs20.x
  region: us-east-1
  environment:
    USERS_TABLE: ${self:service}-users-${self:provider.stage}
  iam:
    role:
      statements:
        - Effect: Allow
          Action:
            - dynamodb:Query
            - dynamodb:Scan
            - dynamodb:GetItem
            - dynamodb:PutItem
            - dynamodb:UpdateItem
            - dynamodb:DeleteItem
          Resource: "arn:aws:dynamodb:${self:provider.region}:*:table/${self:provider.environment.USERS_TABLE}"

functions:
  api:
    handler: api/users.handler
    events:
      - httpApi:
          path: /users
          method: GET
      - httpApi:
          path: /users/{id}
          method: GET
      - httpApi:
          path: /users
          method: POST
      - httpApi:
          path: /users/{id}
          method: PUT
      - httpApi:
          path: /users/{id}
          method: DELETE

resources:
  Resources:
    UsersTable:
      Type: AWS::DynamoDB::Table
      Properties:
        TableName: ${self:provider.environment.USERS_TABLE}
        AttributeDefinitions:
          - AttributeName: id
            AttributeType: S
        KeySchema:
          - AttributeName: id
            KeyType: HASH
        BillingMode: PAY_PER_REQUEST
# Deploy completo com um comando
serverless deploy

# Logs em tempo real
serverless logs -f api --tail

# Remover stack completa
serverless remove

Casos de Uso Perfeitos para Serverless

1. APIs REST/GraphQL

// GraphQL com Apollo Server em Lambda
import { ApolloServer } from '@apollo/server';
import { startServerAndCreateLambdaHandler } from '@as-integrations/aws-lambda';

const typeDefs = `#graphql
  type User {
    id: ID!
    name: String!
    email: String!
  }

  type Query {
    users: [User!]!
    user(id: ID!): User
  }

  type Mutation {
    createUser(name: String!, email: String!): User!
  }
`;

const resolvers = {
  Query: {
    users: async () => {
      // Buscar do DynamoDB
      return await getAllUsers();
    },
    user: async (_, { id }) => {
      return await getUserById(id);
    }
  },
  Mutation: {
    createUser: async (_, { name, email }) => {
      return await createNewUser({ name, email });
    }
  }
};

const server = new ApolloServer({
  typeDefs,
  resolvers
});

export const handler = startServerAndCreateLambdaHandler(server);

2. Processamento de Arquivos

// Lambda triggered por upload S3
import { S3Client, GetObjectCommand, PutObjectCommand } from '@aws-sdk/client-s3';
import sharp from 'sharp';

const s3 = new S3Client({});

export const handler = async (event) => {
  // Event de S3 upload
  const bucket = event.Records[0].s3.bucket.name;
  const key = decodeURIComponent(event.Records[0].s3.object.key);

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

    const imageBuffer = await streamToBuffer(Body);

    // Redimensionar para diferentes tamanhos
    const sizes = [
      { name: 'thumbnail', width: 150 },
      { name: 'medium', width: 500 },
      { name: 'large', width: 1200 }
    ];

    await Promise.all(
      sizes.map(async ({ name, width }) => {
        const resized = await sharp(imageBuffer)
          .resize(width)
          .jpeg({ quality: 80 })
          .toBuffer();

        const newKey = key.replace(/\.\w+$/, `-${name}.jpg`);

        await s3.send(
          new PutObjectCommand({
            Bucket: bucket,
            Key: newKey,
            Body: resized,
            ContentType: 'image/jpeg'
          })
        );
      })
    );

    return { statusCode: 200, body: 'Images processed successfully' };
  } catch (error) {
    console.error(error);
    throw error;
  }
};

async function streamToBuffer(stream) {
  const chunks = [];
  for await (const chunk of stream) {
    chunks.push(chunk);
  }
  return Buffer.concat(chunks);
}

3. Scheduled Tasks (Cron Jobs)

// Lambda executada diariamente
export const handler = async (event) => {
  console.log('Running daily cleanup task...');

  try {
    // Limpar dados antigos
    await cleanupOldData();

    // Enviar relatório
    await sendDailyReport();

    // Backup de dados importantes
    await backupCriticalData();

    return {
      statusCode: 200,
      body: JSON.stringify({ message: 'Daily tasks completed' })
    };
  } catch (error) {
    console.error('Daily task error:', error);
    await notifyAdmins(error);
    throw error;
  }
};

async function cleanupOldData() {
  // Deletar registros com mais de 90 dias
  const ninetyDaysAgo = new Date();
  ninetyDaysAgo.setDate(ninetyDaysAgo.getDate() - 90);

  // Implementação específica
}

async function sendDailyReport() {
  // Gerar e enviar relatório por email
}

async function backupCriticalData() {
  // Backup para S3 Glacier
}

async function notifyAdmins(error) {
  // SNS ou SES para notificação
}

Vantagens e Desvantagens

✅ Vantagens

  1. Custo: Pague apenas pelo que usar
  2. Escalabilidade: Automática e infinita
  3. Manutenção: Zero overhead operacional
  4. Deploy: Extremamente rápido
  5. Segurança: Gerenciada pelo provider

⚠️ Desvantagens

  1. Cold Start: ~100-500ms na primeira execução
  2. Tempo Limite: AWS Lambda = 15 min máximo
  3. Vendor Lock-in: Código acoplado ao provider
  4. Debugging: Mais complexo que tradicional
  5. Custos em Alto Volume: Pode ficar caro

Quando Usar Serverless

Use Serverless quando:

  • Tráfego variável/imprevisível
  • Processamento batch/background
  • APIs com baixo/médio tráfego
  • Webhooks e integrações
  • Protótipos e MVPs

Evite Serverless quando:

  • Processamento muito longo (>15 min)
  • Tráfego altíssimo e constante
  • Necessita state em memória
  • Latência crítica (<10ms)

Se você quer entender mais sobre como otimizar código JavaScript para ambientes serverless, leia Performance em JavaScript: Técnicas de Otimização Avançadas onde você vai descobrir como escrever código mais eficiente.

Bora pra cima! 🦅

💻 Domine JavaScript Para o Cloud

Serverless é JavaScript puro executado na cloud. Quanto melhor seu JavaScript, mais você aproveita serverless.

Se você quer construir uma base sólida em 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