Artículo

Arquitectura de microservicios

Arquitectura de microservicios

La arquitectura de microservicios estructura una aplicación como una colección de servicios débilmente acoplados y que se pueden implementar de forma independiente. Cada servicio posee una capacidad comercial específica, tiene su propio almacén de datos y se comunica a través de una red. Si bien los microservicios introducen una complejidad operativa significativa, permiten a las organizaciones escalar equipos de desarrollo, implementar de forma independiente y construir sistemas más resistentes.

5 min de lecturaIdioma: ES EspañolGratis0 aplausos0 comentarios
TecnologíaArtículos de ingenieríaDevOpsArchitectureMicroservicesTechnologyEngineering Articles
Opciones de lectura

Introducción

La arquitectura de microservicios estructura una aplicación como una colección de servicios débilmente acoplados y que se pueden implementar de forma independiente. Cada servicio posee una capacidad comercial específica, tiene su propio almacén de datos y se comunica a través de una red. Si bien los microservicios introducen una complejidad operativa significativa, permiten a las organizaciones escalar equipos de desarrollo, implementar de forma independiente y construir sistemas más resistentes.


Monolito versus microservicios

El monolito

Una aplicación monolítica tiene todas las funciones en una única base de código y unidad de implementación:

┌─────────────────────────────────────────────┐
│              Monolith Application           │
├──────────────┬──────────────┬───────────────┤
│  User Module │  Order Module│  Payment      │
│  (controllers, services, repos, DB tables)  │
└──────────────┴──────────────┴───────────────┘

Ventajas del monolito:

  • Desarrollo y pruebas simples (proceso único).
  • Fácil depuración (sin llamadas de red entre módulos).
  • Comunicación rápida (llamadas a métodos en memoria).
  • Sin complejidad del sistema distribuido.
  • Ideal para equipos pequeños y productos en etapa inicial.

Desventajas del monolito:

  • Escalar significa escalar todo, no sólo el cuello de botella.
  • Un solo error puede bloquear toda la aplicación.
  • La pila de tecnología está bloqueada.
  • Una base de código grande se vuelve difícil de entender y mantener.
  • La implementación lleva más tiempo a medida que crece el código base.
  • Los gastos generales de coordinación del equipo aumentan.

La arquitectura de microservicios

┌──────────────────┐   ┌──────────────────┐   ┌──────────────────┐
│   User Service   │   │   Order Service  │   │  Payment Service │
│   (own DB, API)  │   │  (own DB, API)   │   │  (own DB, API)   │
└────────┬─────────┘   └────────┬─────────┘   └────────┬─────────┘
         │                      │                      │
         └──────────────────────┼──────────────────────┘
                                │
                    ┌───────────▼───────────┐
                    │     API Gateway       │
                    │   (auth, routing)     │
                    └───────────────────────┘

Principios básicos

1. Responsabilidad única

Cada servicio posee exactamente una capacidad comercial. Si puede describir el propósito de un servicio sin utilizar "y", es probable que tenga un buen alcance.

Bien:

  • servicio de pago: gestiona el procesamiento de pagos y los reembolsos.
  • servicio de notificaciones: envía correos electrónicos, SMS y notificaciones automáticas.
  • inventory-service: gestiona los niveles de stock y las reservas.

Malo:

  • "servicio de usuario y pedido": dos capacidades distintas combinadas (viola SRP).
  • backend-service: todo en un solo servicio (ningún microservicio).

2. Gestión de datos descentralizada

Cada servicio es dueño de su base de datos. Los servicios nunca comparten bases de datos: se comunican a través de API.

-- Service A has its own database
CREATE TABLE users (
    id UUID PRIMARY KEY,
    email VARCHAR(255) UNIQUE,
    name VARCHAR(255)
);

-- Service B has its own database, stores only the user ID
CREATE TABLE orders (
    id UUID PRIMARY KEY,
    user_id UUID NOT NULL,
    total DECIMAL(10,2),
    status VARCHAR(50)
);

¿Por qué no compartir bases de datos?

  • Estrecho acoplamiento: los cambios de esquema en un servicio interrumpen otros.
  • Los diferentes servicios tienen diferentes necesidades de almacenamiento (relacional, de documento o de gráfico).
  • El despliegue independiente se vuelve imposible.
  • El escalado requiere migraciones de esquemas coordinadas.

3. Resiliencia y aislamiento de fallas

Una falla en un servicio no debería afectar a otros.

Patrón de disyuntor:

// Using a circuit breaker to handle service failures gracefully
const breaker = new CircuitBreaker({
  failureThreshold: 5,
  resetTimeout: 30000, // 30 seconds
});

async function getPaymentStatus(orderId: string) {
  try {
    return await breaker.call(() => paymentService.getStatus(orderId));
  } catch (error) {
    // Fallback: return cached status or default
    return { status: 'pending', note: 'Payment service unavailable' };
  }
}

Patrón de mamparo: Asigne grupos de subprocesos separados para diferentes servicios para que un servicio lento no agote todos los recursos:

// Separate thread pools per service
const paymentPool = new ThreadPool({ maxThreads: 10 });
const inventoryPool = new ThreadPool({ maxThreads: 20 });
const notificationPool = new ThreadPool({ maxThreads: 5 });

4. Implementabilidad independiente

Cada servicio se puede implementar de forma independiente. Este es el principal beneficio de los microservicios.

Automatización de la implementación:

# GitHub Actions for a single microservice
name: Deploy Payment Service
on:
  push:
    paths:
      - 'services/payment/**'
      - '.github/workflows/payment.yml'
jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: docker build -t payment-service .
      - run: docker push registry.example.com/payment-service:${{ github.sha }}
      - run: kubectl set image deployment/payment-service payment-service=registry.example.com/payment-service:${{ github.sha }}

Patrones de comunicación

Comunicación síncrona (REST, gRPC)

Pros: Fácil de implementar y razonar. La semántica de solicitud-respuesta es familiar.

Contras: Crea un acoplamiento en tiempo de ejecución: si el servicio descendente no funciona, la persona que llama se ve afectada.

// REST call using fetch
const order = await fetch(`http://order-service:8080/api/orders/${orderId}`);
// gRPC definition
service OrderService {
  rpc GetOrder (GetOrderRequest) returns (Order);
}
message GetOrderRequest {
  string order_id = 1;
}

Comunicación asincrónica (colas de mensajes)

Ventajas: Acoplamiento flexible, mejor resiliencia y admite arquitecturas basadas en eventos.

Contras: La coherencia final, más difícil de depurar, requiere infraestructura de mensajes.

// Kafka producer
await producer.send({
  topic: 'order.created',
  messages: [{ value: JSON.stringify({ orderId, userId, total }) }]
});

// Kafka consumer in notification service
await consumer.run({
  eachMessage: async ({ message }) => {
    const { orderId, userId } = JSON.parse(message.value.toString());
    await emailService.sendOrderConfirmation(userId, orderId);
  }
});

Elegir entre síncrono y asíncrono

Criterios Sincrónico Asincrónico
¿La persona que llama necesita una respuesta inmediata? Sí No
¿Se puede aplazar la operación? No Sí
¿El servicio downstream está siempre disponible? Sí No requerido
¿Necesitas una fuerte consistencia? Sí Eventual está bien

Estrategias de descomposición

Cómo dividir un monolito

  1. Identifique contextos delimitados: utilice el diseño basado en dominios (DDD) para encontrar límites naturales.
  2. Comience con el servicio de mayor valor: extraiga un servicio que tenga un valor independiente claro.
  3. Extraiga un servicio a la vez: no intente una migración a gran escala.
  4. Utilice el patrón Higo estrangulador: dirija gradualmente el tráfico desde el monolito hacia el nuevo servicio.
// Strangler Fig pattern
const gateway = express();

// Route to new service if available, fall back to monolith
gateway.use('/api/users', async (req, res, next) => {
  try {
    const response = await fetch('http://user-service/api/users' + req.path);
    return res.json(await response.json());
  } catch {
    next(); // Fall through to monolith handler
  }
});

Directrices de granularidad del servicio

Tamaño del servicio Tamaño del equipo Frecuencia de implementación Ejemplo
⚡ Pequeño 1-3 desarrolladores Varias veces/día Verificación de usuario
📐 Medio 3-6 desarrolladores Diario/semanal Procesamiento de pedidos
🏗️ Grande 6-10 desarrolladores Semanalmente Plataforma de pago

Regla general: Un servicio debe ser lo suficientemente pequeño como para que un solo equipo pueda poseerlo por completo, y lo suficientemente grande como para proporcionar un valor comercial significativo.


Desafíos operativos

Observabilidad

Los microservicios generan enormes cantidades de telemetría. Tres pilares:

1. Registro:

// Structured logging (JSON)
{
  "timestamp": "2026-05-24T10:30:00.123Z",
  "level": "error",
  "service": "payment-service",
  "trace_id": "abc123",
  "message": "Payment processing failed",
  "error": "stripe: insufficient funds",
  "metadata": { "order_id": "ord_456", "amount": 2999 }
}

2. Métricas (formato Prometheus):

# HELP http_requests_total Total HTTP requests
# TYPE http_requests_total counter
http_requests_total{method="POST",path="/api/payments",status="500"} 42

3. Seguimiento distribuido (OpenTelemetry):

# OpenTelemetry auto-instrumentation
OTEL_EXPORTER_OTLP_ENDPOINT: "http://otel-collector:4318"
OTEL_SERVICE_NAME: "payment-service"
OTEL_TRACES_SAMPLER: "parentbased_traceidratio"
OTEL_TRACES_SAMPLER_ARG: "0.1"

Descubrimiento de servicios

En entornos dinámicos (Kubernetes), las direcciones IP cambian. Los servicios se encuentran entre sí a través de DNS:

  • Kubernetes DNS — pago-servicio.namespace.svc.cluster.local.
  • Cónsul — Registro de servicios con controles de salud.
  • Eureka: descubrimiento de servicios de Netflix (Spring Cloud).

Puerta de enlace API

Un único punto de entrada para todas las solicitudes de los clientes:

Función Ejemplo
Enrutamiento /api/users → servicio-usuario, /api/orders → servicio-pedido
Autenticación Validar tokens JWT antes de enrutar
Limitación de velocidad 1000 solicitudes/min por cliente
Solicitar transformación Convertir JSON a Protobuf
Almacenamiento en caché de respuestas Caché de respuestas GET
ruptura de circuito Dejar de dirigirse a servicios en mal estado

Gestión de configuración

Externalizar toda la configuración:

# ConfigMap in Kubernetes
apiVersion: v1
kind: ConfigMap
metadata:
  name: payment-service-config
data:
  DATABASE_URL: "postgresql://..."
  STRIPE_API_KEY: "${STRIPE_API_KEY}"  # from Secret
  MAX_RETRIES: "3"
  FEATURE_FLAG_NEW_CHECKOUT: "true"

Orquestación de contenedores con Kubernetes

apiVersion: apps/v1
kind: Deployment
metadata:
  name: payment-service
spec:
  replicas: 3
  selector:
    matchLabels:
      app: payment-service
  template:
    metadata:
      labels:
        app: payment-service
    spec:
      containers:
      - name: payment-service
        image: registry.example.com/payment-service:latest
        ports:
        - containerPort: 8080
        resources:
          requests:
            memory: "256Mi"
            cpu: "250m"
          limits:
            memory: "512Mi"
            cpu: "500m"
        livenessProbe:
          httpGet:
            path: /health
            port: 8080
          initialDelaySeconds: 10
          periodSeconds: 5

Cuándo NO utilizar microservicios

  • Equipos pequeños (< 10 desarrolladores): la sobrecarga cognitiva de los microservicios supera los beneficios.
  • Aplicaciones simples — Aplicación CRUD con pocas funciones. Un monolito es más rápido de construir.
  • Startups en etapa inicial: la velocidad de iteración importa más que la escalabilidad.
  • Límites de dominio poco claros: si no puede identificar los límites del servicio, creará un monolito distribuido.
  • Aplicaciones de poco tráfico: la sobrecarga de microservicios (redes, implementación, monitoreo) no está justificada.

Ley de Conway: Las organizaciones diseñan sistemas que reflejan su estructura de comunicación. Si su equipo no está organizado en torno a los límites de los servicios, los microservicios crearán fricciones.


Estrategia de migración

Fase 1: prepararse

  1. Agregue monitoreo y registro centralizado al monolito.
  2. Identificar contextos acotados mediante talleres de DDD.
  3. Extraiga bibliotecas compartidas (autenticación, registro, utilidades).
  4. Configure CI/CD y contenedorización.

Fase 2: Extracto

  1. Extraer el primer servicio (menor riesgo, mayor valor).
  2. Implementa el patrón Strangler Fig.
  3. Ejecute monolito + nuevo servicio en paralelo.
  4. Agregue disyuntores y alternativas.

Fase 3: Escala

  1. Extraiga servicios adicionales de forma iterativa.
  2. Implementar comunicación basada en eventos cuando sea apropiado.
  3. Agregue malla de servicios (Istio, Linkerd) para redes avanzadas.
  4. Invierta en ingeniería de plataformas (backstage, portales de desarrolladores).

Recomendaciones de pila de tecnología

Categoría Opciones Notas
Estructura Spring Boot, NestJS, FastAPI, kit Go Elija según la experiencia del equipo
API DESCANSO, gRPC, GraphQL gRPC para interno, REST para externo
Mensajería Kafka, RabbitMQ, NATS Kafka para un alto rendimiento
Base de datos PostgreSQL, DynamoDB, MongoDB Haga coincidir la base de datos con las necesidades del servicio
Recipiente Estibador Universal
Orquestación Kubernetes EKS, AKS, GKE o autogestionado
Puerta de enlace API Kong, enviado, puerta de enlace API de AWS Enviado para la integración de la malla de servicios
Observabilidad OpenTelemetry, Prometheus, Jaeger OpenTelemetry es el estándar
CI/CD Acciones de GitHub, GitLab CI, ArgoCD ArgoCD para GitOps
Malla de servicio Istio, Linkerd, Cilio Linkerd para simplificar
Gestión Secreta HashiCorp Vault, administrador de secretos de AWS Bóveda para múltiples nubes

Conclusión

Los microservicios son un poderoso patrón arquitectónico que permite una implementación independiente, autonomía del equipo y sistemas escalables. Sin embargo, conllevan una complejidad operativa significativa. Las adopciones de microservicios más exitosas:

  1. Comience con un monolito: comprenda su dominio antes de dividirlo.
  2. Extraiga servicios de forma incremental: un servicio a la vez.
  3. Invierta mucho en automatización: CI/CD, monitoreo e infraestructura como código.
  4. Organizar equipos en torno a los servicios: alinear la estructura del equipo con la arquitectura.
  5. Adopte la coherencia eventual: no es necesario que todo sea muy coherente.

Los microservicios no son la arquitectura predeterminada: son una solución a problemas organizativos y de escala específicos. Elíjalos deliberadamente, no por defecto.

Artículos destacados

6 patrones de programación que aprendí de ingenieros senior
EditorialES
5 minGratis

6 patrones de programación que aprendí de ingenieros senior

TecnologíaCodeProgrammingCoding
0 aplausos
Leer
Crea un asistente privado de notas con IA
DestacadoES
9 minGratis

Crea un asistente privado de notas con IA

Convierte notas Markdown en un asistente consultable sin exponer borradores privados.

TecnologíaGuías de plataformaEscritura con IAAIAutomationData
0 aplausos
Leer
Automatiza la extracción de datos de facturas con OCR
EditorialES
8 minGratis

Automatiza la extracción de datos de facturas con OCR

Crea un flujo que lee facturas, extrae campos y marca valores dudosos para revisión.

TecnologíaArtículos de ingenieríaEscritura con IAAIAutomationData
0 aplausos
Leer

Comentarios

0 comentarios

Todavía no hay comentarios aprobados. Las respuestas nuevas pueden esperar moderación.