Volver al blog

Serverless y Edge Computing: El Futuro de la Arquitectura Backend en 2025

Hola HaWkers, ¿todavía estás gestionando servidores manualmente o ya migraste al futuro?

En 2025, la arquitectura de backend pasó por una transformación radical. Serverless dejó de ser un buzzword para convertirse en el estándar de la industria, y edge computing está llevando esa revolución aún más lejos - ejecutando código a milisegundos de tus usuarios, en cualquier lugar del mundo.

Empresas están migrando activamente a estas tecnologías porque resuelven problemas reales: mejor SEO, performance superior, costos de servidor reducidos, y escalabilidad automática sin dolor de cabeza.

Qué Cambió en 2025: Serverless Creció

Serverless computing permite correr código sin gestionar servidores. Te enfocas en el código, y la plataforma se encarga de provisioning, scaling, monitoring y todo lo demás.

¿Por qué serverless explotó en 2025?

  1. Costo: Paga solo por lo que uses (ejecución real, no uptime)
  2. Escalabilidad: Auto-scaling de 0 a millones de requests
  3. Simplicidad: Deploy con un comando, sin DevOps complejo
  4. Cold Start Resuelto: Plataformas modernas redujeron cold starts a <50ms
  5. DX Mejorada: Herramientas maduras y experiencia de desarrollador excelente
// Ejemplo: Edge Function en Vercel (Next.js 15)
// app/api/user-location/route.ts

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

// Esta función corre en el edge, cerca del usuario
export const runtime = 'edge';

export async function GET(request: NextRequest) {
  // Geolocation automática del edge runtime
  const country = request.geo?.country || 'Unknown';
  const city = request.geo?.city || 'Unknown';
  const latitude = request.geo?.latitude || 0;
  const longitude = request.geo?.longitude || 0;

  // Personalizar respuesta basado en la ubicación
  const greeting = getLocalizedGreeting(country);

  // Buscar datos del banco más cercano geográficamente
  const nearestDbRegion = getNearestRegion(latitude, longitude);

  return NextResponse.json({
    location: {
      country,
      city,
      coordinates: { latitude, longitude }
    },
    greeting,
    dbRegion: nearestDbRegion,
    latency: 'sub-50ms', // ¡Magia del Edge!
    timestamp: new Date().toISOString()
  });
}

function getLocalizedGreeting(country: string): string {
  const greetings: Record<string, string> = {
    BR: 'Olá! 🇧🇷',
    US: 'Hello! 🇺🇸',
    GB: 'Hello! 🇬🇧',
    ES: 'Hola! 🇪🇸',
    FR: 'Bonjour! 🇫🇷',
    DE: 'Hallo! 🇩🇪',
    JP: 'こんにちは! 🇯🇵',
  };

  return greetings[country] || 'Hello! 🌍';
}

function getNearestRegion(lat: number, lon: number): string {
  // Simplificado - en la práctica usa algoritmo de distancia real
  if (lat > 0 && lon < -30) return 'us-east-1';
  if (lat > 0 && lon > 0) return 'eu-west-1';
  if (lat < 0 && lon < 0) return 'sa-east-1';
  return 'ap-southeast-1';
}

Este código corre en más de 100 ubicaciones globales simultáneamente. ¿Usuario en Brasil? Código corre en São Paulo. ¿Usuario en Japón? Código corre en Tokyo. Latencia siempre baja.

Edge Computing: Llevando Serverless al Siguiente Nivel

Edge computing ejecuta código lo más cerca posible del usuario final, no en data centers centralizados. Esto reduce latencia drásticamente y mejora experiencia del usuario.

Principales Plataformas Edge en 2025:

  • Cloudflare Workers: 300+ ubicaciones, runtime V8 aislado
  • Vercel Edge Functions: Integración perfecta con Next.js
  • AWS Lambda@Edge: Integrado con CloudFront CDN
  • Deno Deploy: Runtime moderno con soporte TypeScript nativo
  • Netlify Edge Functions: Deno-based, global distribution

Mira un ejemplo práctico con Cloudflare Workers:

// Cloudflare Worker - A/B Testing en el Edge
export interface Env {
  KV_NAMESPACE: KVNamespace;
  ANALYTICS: AnalyticsEngineDataset;
}

export default {
  async fetch(request: Request, env: Env): Promise<Response> {
    const url = new URL(request.url);

    // Implementar A/B testing en el edge - cero latencia
    const variant = getABTestVariant(request);

    // Log para analytics (asíncrono, no bloquea respuesta)
    env.ANALYTICS.writeDataPoint({
      blobs: [variant, url.pathname],
      doubles: [Date.now()],
      indexes: [request.headers.get('cf-ipcountry') || 'unknown']
    });

    // Buscar contenido personalizado del KV (edge storage)
    const cachedContent = await env.KV_NAMESPACE.get(
      `content:${variant}:${url.pathname}`,
      { type: 'json' }
    );

    if (cachedContent) {
      return new Response(JSON.stringify(cachedContent), {
        headers: {
          'Content-Type': 'application/json',
          'Cache-Control': 'public, max-age=3600',
          'X-Variant': variant,
          'X-Edge-Location': request.cf?.colo || 'unknown'
        }
      });
    }

    // Fetch del origen si no está en cache
    const originResponse = await fetch(
      `https://api.example.com${url.pathname}?variant=${variant}`,
      {
        headers: {
          'X-Forwarded-For': request.headers.get('cf-connecting-ip') || '',
          'User-Agent': request.headers.get('user-agent') || ''
        }
      }
    );

    const data = await originResponse.json();

    // Cache en el edge para próximos requests
    await env.KV_NAMESPACE.put(
      `content:${variant}:${url.pathname}`,
      JSON.stringify(data),
      { expirationTtl: 3600 }
    );

    return new Response(JSON.stringify(data), {
      headers: {
        'Content-Type': 'application/json',
        'X-Variant': variant,
        'X-Cache': 'MISS'
      }
    });
  }
};

function getABTestVariant(request: Request): string {
  // Verificar cookie existente
  const cookies = request.headers.get('cookie') || '';
  const variantMatch = cookies.match(/variant=([AB])/);

  if (variantMatch) {
    return variantMatch[1];
  }

  // Distribución 50/50 basado en el IP
  const ip = request.headers.get('cf-connecting-ip') || '';
  const hash = simpleHash(ip);
  return hash % 2 === 0 ? 'A' : 'B';
}

function simpleHash(str: string): number {
  let hash = 0;
  for (let i = 0; i < str.length; i++) {
    hash = ((hash << 5) - hash) + str.charCodeAt(i);
    hash = hash & hash; // Convertir a entero de 32bit
  }
  return Math.abs(hash);
}

Edge computing

AWS Lambda: El Gigante del Serverless

AWS Lambda aún domina el mercado serverless en 2025, pero evolucionó significativamente:

// AWS Lambda con Node.js 20 y TypeScript
// handler.ts

import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda';
import { DynamoDBClient } from '@aws-sdk/client-dynamodb';
import { DynamoDBDocumentClient, QueryCommand, PutCommand } from '@aws-sdk/lib-dynamodb';
import { S3Client, PutObjectCommand } from '@aws-sdk/client-s3';

// Inicializar clientes fuera del handler (reutilización entre invocaciones)
const dynamoClient = new DynamoDBClient({ region: 'us-east-1' });
const docClient = DynamoDBDocumentClient.from(dynamoClient);
const s3Client = new S3Client({ region: 'us-east-1' });

interface OrderEvent {
  userId: string;
  items: Array<{ productId: string; quantity: number; price: number }>;
  total: number;
}

export const handler = async (
  event: APIGatewayProxyEvent,
  context: Context
): Promise<APIGatewayProxyResult> => {
  // Context info útil para debugging
  console.log('Request ID:', context.requestId);
  console.log('Function name:', context.functionName);
  console.log('Memory limit:', context.memoryLimitInMB);

  try {
    const body: OrderEvent = JSON.parse(event.body || '{}');

    // Validación
    if (!body.userId || !body.items?.length) {
      return {
        statusCode: 400,
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ error: 'Invalid order data' })
      };
    }

    // Crear pedido en DynamoDB
    const orderId = generateOrderId();
    const timestamp = new Date().toISOString();

    await docClient.send(new PutCommand({
      TableName: process.env.ORDERS_TABLE!,
      Item: {
        orderId,
        userId: body.userId,
        items: body.items,
        total: body.total,
        status: 'pending',
        createdAt: timestamp,
        ttl: Math.floor(Date.now() / 1000) + (90 * 24 * 60 * 60) // 90 días
      }
    }));

    // Guardar recibo en S3 para auditoría
    const receipt = {
      orderId,
      ...body,
      timestamp
    };

    await s3Client.send(new PutObjectCommand({
      Bucket: process.env.RECEIPTS_BUCKET!,
      Key: `receipts/${body.userId}/${orderId}.json`,
      Body: JSON.stringify(receipt, null, 2),
      ContentType: 'application/json'
    }));

    // Invocar otra Lambda para procesamiento asíncrono (opcional)
    // await lambdaClient.send(new InvokeCommand({...}));

    return {
      statusCode: 201,
      headers: {
        'Content-Type': 'application/json',
        'X-Request-Id': context.requestId
      },
      body: JSON.stringify({
        orderId,
        status: 'created',
        message: 'Order created successfully'
      })
    };

  } catch (error) {
    console.error('Error processing order:', error);

    return {
      statusCode: 500,
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        error: 'Internal server error',
        requestId: context.requestId
      })
    };
  }
};

function generateOrderId(): string {
  return `ORD-${Date.now()}-${Math.random().toString(36).substr(2, 9).toUpperCase()}`;
}

// Configuración serverless.yml o SAM template
/*
functions:
  createOrder:
    handler: handler.handler
    runtime: nodejs20.x
    memorySize: 1024
    timeout: 30
    environment:
      ORDERS_TABLE: ${self:custom.ordersTable}
      RECEIPTS_BUCKET: ${self:custom.receiptsBucket}
    events:
      - httpApi:
          path: /orders
          method: post
    iamRoleStatements:
      - Effect: Allow
        Action:
          - dynamodb:PutItem
          - dynamodb:Query
        Resource: !GetAtt OrdersTable.Arn
      - Effect: Allow
        Action:
          - s3:PutObject
        Resource: !Sub '${ReceiptsBucket.Arn}/*'
*/

Deno Deploy: El Nuevo Player Prometedor

Deno Deploy emergió como alternativa fuerte en 2025, con TypeScript nativo y APIs web estándar:

// Deno Deploy - API RESTful completa
// main.ts

import { serve } from 'https://deno.land/std@0.200.0/http/server.ts';
import { create, verify } from 'https://deno.land/x/djwt@v3.0.1/mod.ts';

const kv = await Deno.openKv(); // Deno KV - banco key-value global

interface User {
  id: string;
  email: string;
  name: string;
  createdAt: string;
}

// Crypto key para JWT
const key = await crypto.subtle.generateKey(
  { name: 'HMAC', hash: 'SHA-512' },
  true,
  ['sign', 'verify']
);

async function handleRequest(req: Request): Promise<Response> {
  const url = new URL(req.url);
  const path = url.pathname;
  const method = req.method;

  // CORS headers
  const headers = {
    'Content-Type': 'application/json',
    'Access-Control-Allow-Origin': '*',
    'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS'
  };

  if (method === 'OPTIONS') {
    return new Response(null, { status: 204, headers });
  }

  try {
    // Rutas
    if (path === '/api/users' && method === 'POST') {
      return await createUser(req, headers);
    }

    if (path.startsWith('/api/users/') && method === 'GET') {
      const userId = path.split('/')[3];
      return await getUser(userId, headers);
    }

    if (path === '/api/auth/login' && method === 'POST') {
      return await login(req, headers);
    }

    return new Response(
      JSON.stringify({ error: 'Not found' }),
      { status: 404, headers }
    );

  } catch (error) {
    console.error('Error:', error);
    return new Response(
      JSON.stringify({ error: 'Internal server error' }),
      { status: 500, headers }
    );
  }
}

async function createUser(req: Request, headers: HeadersInit): Promise<Response> {
  const body = await req.json();

  const user: User = {
    id: crypto.randomUUID(),
    email: body.email,
    name: body.name,
    createdAt: new Date().toISOString()
  };

  // Guardar en Deno KV (replicado globalmente)
  await kv.set(['users', user.id], user);
  await kv.set(['users_by_email', user.email], user.id);

  return new Response(
    JSON.stringify({ user }),
    { status: 201, headers }
  );
}

async function getUser(userId: string, headers: HeadersInit): Promise<Response> {
  const result = await kv.get(['users', userId]);

  if (!result.value) {
    return new Response(
      JSON.stringify({ error: 'User not found' }),
      { status: 404, headers }
    );
  }

  return new Response(
    JSON.stringify({ user: result.value }),
    { status: 200, headers }
  );
}

async function login(req: Request, headers: HeadersInit): Promise<Response> {
  const body = await req.json();

  // Buscar usuario por email
  const userIdResult = await kv.get(['users_by_email', body.email]);

  if (!userIdResult.value) {
    return new Response(
      JSON.stringify({ error: 'Invalid credentials' }),
      { status: 401, headers }
    );
  }

  const userResult = await kv.get(['users', userIdResult.value as string]);
  const user = userResult.value as User;

  // Generar JWT
  const jwt = await create(
    { alg: 'HS512', typ: 'JWT' },
    {
      sub: user.id,
      email: user.email,
      exp: Math.floor(Date.now() / 1000) + (60 * 60 * 24) // 24 horas
    },
    key
  );

  return new Response(
    JSON.stringify({ token: jwt, user }),
    { status: 200, headers }
  );
}

// Iniciar servidor
serve(handleRequest, { port: 8000 });

Costos: Serverless vs Servidor Tradicional

Comparemos costos reales en 2025:

Aplicación de ejemplo:

  • 10 millones de requests/mes
  • 500ms tiempo promedio de ejecución
  • 512MB memoria

AWS Lambda:

  • Compute: ~$100/mes
  • API Gateway: ~$35/mes
  • DynamoDB: ~$25/mes (on-demand)
  • Total: ~$160/mes

EC2 Tradicional:

  • Instancia t3.medium (2 vCPU, 4GB): ~$30/mes
  • Load Balancer: ~$25/mes
  • RDS (db.t3.small): ~$45/mes
  • PERO: Necesitas gestionar, escalar, monitorear
  • Total: ~$100/mes (pero con overhead operacional)

Vercel/Netlify (Plan Pro):

  • $20/miembro/mes + bandwidth
  • Edge functions incluidas
  • Total: ~$50-150/mes dependiendo del tráfico

Mejores Prácticas para Serverless en 2025

1. Optimiza Cold Starts:

// ❌ Malo - imports pesados aumentan cold start
import _ from 'lodash';
import moment from 'moment';
import { huge_library } from 'huge-lib';

// ✅ Bueno - imports mínimos y tree-shaking
import { debounce } from 'lodash-es/debounce';
import { formatISO } from 'date-fns';

2. Reutiliza Conexiones:

// ❌ Malo - nueva conexión en cada invocación
export const handler = async () => {
  const db = await connectToDatabase();
  // ...
};

// ✅ Bueno - conexión reutilizada
const db = await connectToDatabase();

export const handler = async () => {
  // Usar 'db' existente
};

3. Usa Caching Agresivo:

// Edge caching con Vercel
export const config = {
  runtime: 'edge',
};

export default async function handler(req: Request) {
  const data = await fetchData();

  return new Response(JSON.stringify(data), {
    headers: {
      'Cache-Control': 'public, s-maxage=3600, stale-while-revalidate=86400'
    }
  });
}

4. Monitorea Performance:

Usa herramientas como:

  • AWS CloudWatch
  • Vercel Analytics
  • Datadog Serverless
  • Sentry para error tracking

El Futuro en 2026 y Más Allá

Tendencias emergentes:

  • WebAssembly en el Edge: Correr código compilado con performance nativa
  • Serverless Containers: AWS Fargate, Cloud Run evolucionando
  • Edge Databases: Turso, Neon, PlanetScale con edge caching
  • IA en el Edge: Correr modelos de ML cerca de los usuarios
  • Multi-cloud Serverless: Abstracciones para correr en cualquier cloud

Si quieres entender mejor arquitecturas escalables, échale un vistazo a Microservices vs Monolito: Cuándo Usar Cada Uno donde exploramos patrones arquitecturales.

¡Vamos a por ello! 🦅

Comentarios (0)

Este artículo aún no tiene comentarios 😢. ¡Sé el primero! 🚀🦅

Añadir comentarios