Software Engineering · Microservices|13 min de lectura|

Arquitectura de Microservicios: Cuándo Tiene Sentido y Cómo Hacerlo Bien

Los microservicios se convirtieron en el estándar de moda hace una década. En 2025, los equipos con más experiencia son más cautelosos: los beneficios son reales pero los costos operacionales son igualmente reales. Esta guía no vende microservicios — documenta cuándo tienen sentido, cómo diseñarlos para que los beneficios superen los costos, y los anti-patrones que convierten una arquitectura distribuida en una fuente constante de incidentes.

El costo oculto que nadie menciona al principio

Un monolito bien construido necesita: un proceso de deploy, una base de datos, un sistema de logging, un sistema de monitoreo. La misma funcionalidad en 10 microservicios necesita: 10 pipelines de deploy, 10 bases de datos o schemas separados, correlación de logs entre 10 servicios, 10 conjuntos de alertas, y una capa de comunicación entre todos ellos que en el monolito era simplemente una llamada a función.

Amazon, Netflix y Spotify construyeron microservicios cuando tenían cientos de ingenieros y la escala hacía que el monolito fuera el verdadero cuello de botella. La trampa es intentar replicar su arquitectura con un equipo de 5-10 personas donde el overhead operacional elimina la mayor parte del tiempo de desarrollo.

Cuándo los microservicios SÍ tienen sentido

  • Múltiples equipos trabajando en el mismo producto, con dominios bien definidos y diferente cadencia de deploy. Los microservicios permiten que el equipo de pagos deploya independientemente del equipo de catálogo.
  • Componentes con requerimientos técnicos fundamentalmente distintos: el servicio de procesamiento de video necesita GPU y escala a miles de instancias efímeramente; el servicio de gestión de usuarios es liviano y constante.
  • Escala diferencial: el motor de búsqueda necesita escalar a 100x en eventos de alto tráfico; el módulo de configuración de cuentas maneja 10 requests por hora.
  • El monolito tiene problemas reales de escala o deploy que generan incidentes, no proyectados que justifican la complejidad desde el inicio.

Domain-Driven Design: el cimiento del diseño correcto

El error más frecuente al descomponer un monolito en microservicios: definir los servicios por capas técnicas (user-service, data-service, email-service) en lugar de por dominios de negocio. Los servicios definidos por capa técnica generan alto acoplamiento: para agregar una funcionalidad de checkout, necesitas modificar el user-service, el product-service, el order-service y el payment-service simultáneamente.

Domain-Driven Design define los Bounded Contexts — los límites naturales del negocio donde un concepto tiene una definición consistente. Un 'usuario' en el contexto de Autenticación es diferente a un 'usuario' en el contexto de Notificaciones o en el de Pagos. Cada Bounded Context se convierte en un microservicio (o en un módulo bien definido dentro del monolito).

text
# Bounded Contexts identificados por dominio de negocio
┌─────────────────────┐  ┌─────────────────────┐  ┌─────────────────────┐
│   Identity Context  │  │   Catalog Context   │  │   Orders Context    │
│   ─────────────────  │  │   ─────────────────  │  │   ─────────────────  │
│   Usuario           │  │   Producto          │  │   Orden             │
│   Sesión            │  │   Categoría         │  │   LineItem          │
│   Permiso           │  │   Inventario        │  │   Precio aplicado   │
└─────────────────────┘  └─────────────────────┘  └─────────────────────┘
         │                         │                        │
         └──────── Event Bus (Kafka/RabbitMQ) ─────────────┘
# Los servicios NO comparten base de datos
# Se comunican via eventos para operaciones asíncronas
# Se comunican via API para queries síncronas

Comunicación síncrona vs. asíncrona: la decisión más importante

Comunicación síncrona (REST/gRPC): un servicio espera la respuesta del otro antes de continuar. Más fácil de depurar, consistencia inmediata, pero crea acoplamiento temporal — si el servicio destino está caído, el servicio origen también falla.

Comunicación asíncrona (eventos via Kafka/RabbitMQ): el servicio emisor publica un evento y continúa sin esperar. El servicio consumidor procesa el evento en su propio tiempo. Más resiliente, permite escalar consumidores independientemente, pero la depuración es más compleja y la consistencia es eventual.

typescript
// Patrón Outbox para garantía de entrega de eventos
// El evento se persiste en la misma transacción que los datos
async function crearOrden(data: CreateOrderDto, trx: Transaction) {
  // 1. Crear la orden en la base de datos
  const orden = await Orden.create(data, { transaction: trx });

  // 2. Persistir el evento en la tabla outbox (misma transacción)
  await OutboxEvent.create({
    aggregate_type: 'Order',
    aggregate_id: orden.id,
    event_type: 'OrderCreated',
    payload: JSON.stringify(orden.toJSON()),
  }, { transaction: trx });

  // Si la transacción hace commit, el evento está garantizado
  // Un worker separado lee la tabla outbox y publica a Kafka
  // Si Kafka está caído, el worker reintenta al recuperarse
}
El patrón de publicar a Kafka directamente dentro de la transacción y hacer commit de la DB también es un error clásico: si Kafka está caído, la transacción falla y los datos no se persisten. Si la DB falla después del commit de Kafka, el evento está en Kafka pero la operación no se completó. El patrón Outbox es la solución correcta.

Service Mesh: cuándo añadir Istio o Linkerd

Un service mesh añade una capa de proxy entre todos los microservicios que gestiona: mTLS (cifrado y autenticación entre servicios), traffic management (retries, circuit breaking, load balancing), y observabilidad (métricas de latencia y errores por par de servicios).

La decisión de cuándo añadir service mesh: es necesario cuando tienes más de 5-6 microservicios y los requerimientos de seguridad (cifrado en tránsito interno, autenticación servicio-a-servicio) o la complejidad del traffic management justifican el overhead operacional. Para 2-3 microservicios, gestionar TLS y retries a nivel de aplicación es más pragmático.

El anti-patrón del microservicio nano

El anti-patrón más destructivo: descomponer hasta el nivel de función. Un servicio que solo expone 2-3 endpoints con lógica trivial no aporta los beneficios de los microservicios — solo añade latencia de red, complejidad de deploy, y un servicio más que monitorear. La regla práctica: si un servicio no puede justificar su propio pipeline de CI/CD y su propia base de datos o schema, probablemente es demasiado pequeño.

Preguntas frecuentes

¿Cómo migro de un monolito a microservicios sin parar el negocio?
El Strangler Fig Pattern aplicado a microservicios: identificar el primer módulo a extraer (típicamente el de mayor escala diferencial o el que bloquea a más equipos), extraerlo a un servicio separado con una API bien definida, redirigir el tráfico gradualmente mientras el monolito sigue funcionando como fallback. La clave es mantener la base de datos compartida inicialmente y migrarla al nuevo servicio en una segunda fase.
¿Microservicios o modular monolith para una startup?
Modular monolith casi siempre para una startup. Un monolito bien modularizado (módulos con interfaces limpias, sin acoplamiento directo entre módulos) puede evolucionar a microservicios cuando el equipo y la escala lo justifiquen — extrayendo módulos con límites ya bien definidos. Los microservicios desde el día uno en una startup son optimización prematura que consume tiempo de ingeniería que debería ir al producto.
¿Cómo gestiono las transacciones que cruzan múltiples microservicios?
Las transacciones distribuidas (2PC) son extremadamente difíciles de implementar correctamente en microservicios. El patrón recomendado es Saga: cada servicio ejecuta su transacción local y publica un evento. Si falla algún paso, los servicios anteriores ejecutan transacciones compensatorias para revertir. Requiere que todas las operaciones tengan una operación inversa definida.
¿Cómo hago testing de integración entre microservicios?
Contract testing (Pact) para verificar que los consumidores y proveedores de API son compatibles sin necesitar un ambiente de integración completo. Tests E2E en un ambiente de staging con todos los servicios desplegados para los flujos críticos. Los tests de integración que levantan todos los microservicios en local son lentos y frágiles — el contract testing es más pragmático.
¿Qué base de datos usa cada microservicio?
Cada microservicio debería tener su propio datastore — el principio de Database per Service. El tipo de base de datos puede variar: servicio de catálogo con PostgreSQL relacional, servicio de búsqueda con Elasticsearch, servicio de sesiones con Redis, servicio de eventos con Kafka. La base de datos compartida entre microservicios es el anti-patrón más destructivo porque crea acoplamiento a nivel de schema que hace imposible el deploy independiente.

¿Tu empresa está evaluando migrar a microservicios? Podemos hacer el assessment arquitectónico y diseñar la estrategia de migración correcta para tu contexto.

Habla con el equipo

Artículos relacionados

IQS

Equipo de Ingeniería — IQS

Ingenieros de software, cloud y DevOps con experiencia en proyectos empresariales.