Anti-patrones en Arquitectura de Software
Errores comunes en arquitectura de software distribuida: cómo identificarlos, su impacto real y las estrategias para corregirlos.
Qué son los anti-patrones
Un anti-patrón es una solución recurrente que parece razonable al principio pero que genera problemas significativos a mediano y largo plazo. A diferencia de un simple error, los anti-patrones son trampas comunes en las que caen equipos experimentados, muchas veces por presión de tiempo, falta de contexto o decisiones que fueron correctas en un momento pero dejaron de serlo.
Reconocer estos anti-patrones a tiempo te permite evitar deuda técnica costosa y tomar decisiones arquitectónicas más informadas.
Por qué surgen los anti-patrones
Los anti-patrones no aparecen por incompetencia. Surgen por razones comprensibles:
| Causa | Ejemplo |
|---|---|
| Presión de tiempo | ”Agreguemos esta funcionalidad al servicio existente, no hay tiempo para crear uno nuevo” |
| Falta de contexto | ”Todos los servicios pueden leer de esta base de datos, es más fácil” |
| Decisiones que envejecen | ”Cuando éramos 3 desarrolladores esto funcionaba bien, ahora somos 30” |
| Optimización prematura | ”Separemos todo en microservicios desde el día 1” |
| Inercia organizacional | ”Siempre lo hemos hecho así” |
1. Monolito Distribuido (Distributed Monolith)
Problema
Ocurre cuando un sistema se divide en múltiples servicios que dependen tan fuertemente entre sí que no pueden desplegarse, escalarse ni evolucionar de forma independiente. En la práctica, tienes la complejidad operativa de los microservicios sin ninguno de sus beneficios.
┌──────────┐ ┌──────────┐ ┌──────────┐
│Servicio A│────►│Servicio B│────►│Servicio C│
│ │◄────│ │◄────│ │
└──────────┘ └──────────┘ └──────────┘
│ │ │
└────────────────┴────────────────┘
Despliegue conjunto obligatorio
Modelos de datos compartidos
Llamadas síncronas en cadena
Las señales más claras son:
- Necesitas desplegar varios servicios a la vez para que un cambio funcione
- Un cambio en el contrato de un servicio rompe a los demás
- Los servicios comparten librerías internas con lógica de negocio
- Los tests de integración requieren levantar todo el sistema
Impacto
- Despliegues frágiles: Un cambio en un servicio requiere coordinar el despliegue de otros, eliminando la agilidad que motivó la separación
- Fallos en cascada: La dependencia síncrona entre servicios hace que la caída de uno arrastre a los demás
- Complejidad sin beneficio: El equipo paga el costo operativo de múltiples servicios sin ganar independencia real
- Velocidad de desarrollo reducida: Los equipos no pueden trabajar en paralelo porque los cambios se bloquean mutuamente
Cómo detectarlo
- Pregunta: ¿puedo desplegar este servicio sin tocar ningún otro? Si la respuesta es “no” con frecuencia, tienes un monolito distribuido
- Revisa si los servicios comparten esquemas de base de datos o librerías con lógica de dominio
- Observa si los releases requieren coordinación entre múltiples equipos para un solo cambio funcional
- Mide el coupling score: cuenta cuántos servicios se despliegan juntos en cada release
Corrección
| Estrategia | Descripción |
|---|---|
| Definir contratos claros | Usar APIs versionadas con contratos explícitos. Cada servicio expone una interfaz estable y evoluciona internamente sin romper consumidores |
| Comunicación asíncrona | Reemplazar llamadas síncronas en cadena por eventos. Publicar un evento en lugar de hacer una llamada directa |
| Bounded Contexts (DDD) | Aplicar DDD para identificar los límites reales del dominio. Cada servicio debe ser dueño de su contexto |
| Evaluar re-consolidación | Si los servicios están tan acoplados que no pueden separarse, considera volver a unirlos. Un monolito bien diseñado es mejor que un monolito distribuido |
| Anti-Corruption Layer | Usar ACL entre servicios para traducir modelos y reducir el acoplamiento directo |
2. Base de Datos Compartida (Shared Database)
Problema
Múltiples servicios leen y escriben directamente en la misma base de datos, accediendo a las mismas tablas. Esto crea un acoplamiento invisible: los servicios no dependen de APIs explícitas sino de la estructura interna de las tablas.
┌──────────┐ ┌──────────┐ ┌──────────┐
│Servicio A│ │Servicio B│ │Servicio C│
└────┬─────┘ └────┬─────┘ └────┬─────┘
│ │ │
▼ ▼ ▼
┌──────────────────────────────────────┐
│ Base de Datos Compartida │
│ │
│ ┌─────────┐ ┌─────────┐ ┌────────┐ │
│ │ Tabla X │ │ Tabla Y │ │ Tabla Z│ │
│ └─────────┘ └─────────┘ └────────┘ │
│ │
│ Todos leen y escriben en todas │
└──────────────────────────────────────┘
Las señales más claras son:
- Varios servicios hacen queries directos a las mismas tablas
- Un cambio de esquema requiere modificar múltiples servicios
- No hay un “dueño” claro de cada tabla o conjunto de datos
- Las migraciones de base de datos son eventos de alto riesgo
Impacto
- Acoplamiento oculto: Los servicios parecen independientes pero están atados por la estructura de la base de datos. Un
ALTER TABLEpuede romper servicios que ni siquiera sabías que usaban esa tabla - Escalabilidad limitada: No puedes escalar la base de datos de forma independiente por servicio
- Migraciones peligrosas: Cualquier cambio de esquema es un riesgo porque no sabes quién depende de qué
- Imposibilidad de evolucionar: Cada servicio queda atado a la tecnología y estructura de la base de datos compartida
Cómo detectarlo
- Revisa si más de un servicio tiene connection strings a la misma base de datos
- Busca queries que acceden a tablas que “pertenecen” a otro dominio funcional
- Pregunta: ¿quién es el dueño de esta tabla? Si la respuesta es “varios servicios”, tienes este anti-patrón
- Ejecuta un análisis de dependencias: mapea qué servicios acceden a qué tablas
Corrección
| Estrategia | Descripción |
|---|---|
| Database per Service | Cada servicio tiene su propia base de datos. Solo el servicio dueño puede leer y escribir sus datos |
| Exponer datos vía API | Si el Servicio B necesita datos del Servicio A, los solicita a través de la API de A |
| Eventos para sincronización | Usar eventos de dominio para que los servicios mantengan copias locales de los datos que necesitan |
| Outbox Pattern | Publicar eventos de forma confiable cuando los datos cambian, para que otros servicios se sincronicen |
| Migración incremental | No intentes separar todo de golpe. Identifica las tablas más problemáticas y migra servicio por servicio |
3. God Service (Servicio Dios)
Problema
Un solo servicio concentra demasiadas responsabilidades y se convierte en el punto central por el que pasa casi toda la lógica del sistema. Suele empezar como un servicio legítimo que va acumulando funcionalidades porque “es más fácil agregarlo aquí”.
┌─────────────────────┐
│ God Service │
│ │
│ • Autenticación │
│ • Usuarios │
│ • Pedidos │
│ • Pagos │
│ • Notificaciones │
│ • Reportes │
│ • Inventario │
└──────────┬──────────┘
│
┌────────────────┼────────────────┐
▼ ▼ ▼
┌──────────┐ ┌──────────┐ ┌──────────┐
│Servicio X│ │Servicio Y│ │Servicio Z│
│(simple) │ │(simple) │ │(simple) │
└──────────┘ └──────────┘ └──────────┘
Todos dependen del God Service
Las señales más claras son:
- Un servicio tiene significativamente más endpoints que los demás
- La mayoría de los cambios funcionales requieren modificar ese servicio
- El servicio tiene múltiples responsabilidades no relacionadas entre sí
- El equipo que lo mantiene es un cuello de botella constante
Impacto
- Cuello de botella de desarrollo: Todos los equipos necesitan modificar el mismo servicio
- Punto único de fallo: Si el God Service cae, gran parte del sistema deja de funcionar
- Escalabilidad desigual: No puedes escalar solo pagos si está empaquetado con reportes
- Complejidad creciente: El servicio se vuelve difícil de entender, testear y mantener
- Despliegues riesgosos: Cada despliegue toca muchas funcionalidades
Cómo detectarlo
- Mide las líneas de código o endpoints por servicio. Si uno tiene 10x más que el promedio, es sospechoso
- Revisa los commits: si un servicio concentra la mayoría de los cambios de múltiples equipos, probablemente es un God Service
- Pregunta: ¿puedo describir la responsabilidad de este servicio en una frase? Si necesitas una lista larga, tiene demasiadas responsabilidades
- Analiza las dependencias: si la mayoría de los servicios dependen de uno solo, es un God Service
Corrección
| Estrategia | Descripción |
|---|---|
| Identificar bounded contexts (DDD) | Mapear los dominios reales dentro del God Service. Cada dominio es candidato a ser un servicio independiente |
| Strangler Fig Pattern | Extraer funcionalidades una por una, creando nuevos servicios que interceptan las llamadas al God Service |
| Principio de responsabilidad única | Cada servicio debe tener una razón clara para existir |
| Priorizar por impacto | Empieza extrayendo la funcionalidad que cambia con más frecuencia o que tiene requisitos de escalabilidad diferentes |
| API Gateway / BFF | Usar un gateway para enrutar requests a servicios especializados en lugar de al God Service |
4. Servicios Parlanchines (Chatty Services)
Problema
Los servicios se comunican entre sí con demasiadas llamadas de granularidad fina para completar una sola operación. En lugar de una solicitud que obtenga toda la información necesaria, un servicio hace múltiples llamadas pequeñas.
Servicio A necesita mostrar el perfil de un usuario:
Servicio A → Servicio Usuarios: GET /users/123
Servicio A → Servicio Usuarios: GET /users/123/preferences
Servicio A → Servicio Pedidos: GET /orders?user=123
Servicio A → Servicio Pedidos: GET /orders/456/details
Servicio A → Servicio Pedidos: GET /orders/789/details
Servicio A → Servicio Pagos: GET /payments?user=123
Servicio A → Servicio Reviews: GET /reviews?user=123
Total: 7 llamadas de red para una sola pantalla
Las señales más claras son:
- Una operación de negocio genera más de 3-4 llamadas entre servicios
- La latencia es alta aunque cada servicio individual responde rápido
- Los logs muestran ráfagas de llamadas correlacionadas para un solo request
Impacto
- Latencia acumulada: 7 llamadas de 50ms cada una suman 350ms en el mejor caso (secuencial)
- Fragilidad: Con 7 llamadas y 99% de disponibilidad por servicio, la disponibilidad efectiva baja a ~93%
- Carga de red excesiva: El tráfico interno puede superar al tráfico externo
- Dificultad para depurar: Rastrear un error a través de múltiples llamadas requiere trazabilidad distribuida
Cómo detectarlo
- Cuenta las llamadas entre servicios que genera un solo request del usuario
- Mide la latencia end-to-end vs. la latencia individual de cada servicio
- Revisa si los servicios exponen endpoints muy granulares
Corrección
| Estrategia | Descripción |
|---|---|
| BFF (Backend for Frontend) | Crear un servicio intermedio que agregue datos de múltiples servicios en una sola respuesta |
| Endpoints agregados | Diseñar endpoints que devuelvan toda la información necesaria para un caso de uso |
| Caché local | Mantener copias locales de datos que cambian poco |
| Llamadas en paralelo | Si las llamadas son independientes, ejecutarlas en paralelo |
| Eventos para datos derivados | Usar eventos para que cada servicio mantenga una vista materializada de los datos que necesita |
5. Modelo Anémico (Anemic Domain Model)
Problema
Las entidades del dominio son contenedores de datos sin comportamiento — solo tienen getters y setters. Toda la lógica de negocio vive en servicios externos que manipulan estas entidades pasivas.
// ❌ Modelo anémico
class Pedido {
id: string;
estado: string;
líneas: LíneaPedido[];
total: number;
// Solo getters y setters, sin lógica
}
class PedidoService {
confirmar(pedido: Pedido) {
if (pedido.estado !== 'borrador') throw new Error('...');
if (pedido.líneas.length === 0) throw new Error('...');
pedido.estado = 'confirmado';
pedido.total = this.calcularTotal(pedido.líneas);
// Toda la lógica está aquí, no en Pedido
}
}
// ✅ Modelo rico
class Pedido {
confirmar() {
if (this.estado !== 'borrador')
throw new Error('Solo se puede confirmar un pedido en borrador');
if (this.líneas.length === 0)
throw new Error('El pedido debe tener al menos una línea');
this.estado = 'confirmado';
this.total = this.calcularTotal();
this.registrarEvento(new PedidoConfirmado(this.id));
}
}
Impacto
- Lógica dispersa: Las reglas de negocio se duplican en múltiples servicios
- Difícil de testear: Los tests necesitan configurar servicios externos para validar reglas simples
- Violación de encapsulamiento: Cualquiera puede modificar el estado de la entidad sin pasar por las reglas
- Difícil de descubrir: No es obvio qué operaciones son válidas sobre una entidad
Corrección
| Estrategia | Descripción |
|---|---|
| Mover lógica a las entidades | Las reglas de negocio que operan sobre los datos de una entidad deben vivir en la entidad |
| Aplicar DDD | Usar agregados con invariantes que protejan la consistencia |
| Value Objects | Encapsular conceptos como Dinero, Email, Dirección con validación propia |
| Domain Events | Las entidades publican eventos cuando cambian de estado |
6. Integración por Base de Datos (Database Integration)
Problema
Los servicios se integran leyendo y escribiendo en tablas de otros servicios en lugar de comunicarse por APIs o eventos. Es una variante más sutil de la base de datos compartida.
Servicio A necesita saber si un usuario es premium:
❌ SELECT tipo FROM usuarios WHERE id = 123
(accede directamente a la tabla del Servicio B)
✅ GET /api/users/123/subscription
(consulta al Servicio B por su API)
Impacto
- Acoplamiento invisible: No hay contrato explícito, solo conocimiento implícito de la estructura de tablas
- Imposible versionar: No puedes evolucionar el esquema sin romper a los consumidores ocultos
- Sin control de acceso: Cualquier servicio puede leer o escribir cualquier dato
Corrección
| Estrategia | Descripción |
|---|---|
| APIs explícitas | Cada servicio expone una API para acceder a sus datos |
| Eventos de dominio | Publicar cambios como eventos para que otros servicios se suscriban |
| ACL Pattern | Crear una capa de traducción entre el modelo interno y los consumidores |
Resumen comparativo
| Anti-patrón | Señal principal | Riesgo mayor | Primera acción |
|---|---|---|---|
| Monolito Distribuido | No puedes desplegar un servicio solo | Complejidad sin beneficio | Definir contratos y bounded contexts |
| Base de Datos Compartida | Varios servicios acceden a las mismas tablas | Migraciones peligrosas | Asignar ownership de tablas |
| God Service | Un servicio concentra toda la lógica | Punto único de fallo | Identificar dominios para extraer |
| Servicios Parlanchines | Muchas llamadas para una operación | Latencia y fragilidad | Crear endpoints agregados o BFF |
| Modelo Anémico | Entidades sin comportamiento | Lógica dispersa y duplicada | Mover lógica a las entidades (DDD) |
| Integración por BD | Queries directos a tablas de otros servicios | Acoplamiento invisible | Exponer APIs explícitas |
Cómo prevenir anti-patrones
Los anti-patrones rara vez aparecen de golpe. Se acumulan gradualmente. Algunas prácticas que ayudan a prevenirlos:
- Revisiones de arquitectura periódicas: Evalúa regularmente si los servicios mantienen su independencia
- Fitness functions: Define métricas automatizadas que detecten acoplamiento (número de dependencias, tamaño de servicios, frecuencia de despliegues conjuntos)
- ADRs (Architecture Decision Records): Documenta las decisiones arquitectónicas y sus razones
- Ownership claro: Cada servicio, tabla y API debe tener un equipo responsable
- Principio de la navaja: Si no puedes justificar por qué un servicio es independiente, probablemente no debería serlo
- Monitoreo de acoplamiento: Mide cuántos servicios se despliegan juntos, cuántas llamadas síncronas hay entre servicios, y cuántas tablas se comparten
Relación con otros patrones
Los anti-patrones se previenen y corrigen aplicando los patrones correctos:
Anti-patrón Patrón que lo corrige
─────────────────────────────────────────────────────
Monolito Distribuido → DDD (Bounded Contexts)
→ Event-Driven Architecture
→ ACL Pattern
Base de Datos Compartida → Database per Service
→ Outbox Pattern (sincronización)
→ Event-Driven Architecture
God Service → Microservices Pattern
→ DDD (descomposición por dominio)
→ API Gateway / BFF
Servicios Parlanchines → BFF Pattern
→ API Gateway Pattern
→ Event-Driven Architecture
Modelo Anémico → DDD (diseño táctico)
→ Aggregates + Domain Events
Integración por BD → ACL Pattern
→ Outbox Pattern
→ Shared Kernel (controlado)
Próximos pasos
Ahora que conoces los anti-patrones más comunes, profundiza en los patrones que los previenen: DDD para definir bounded contexts claros, Microservices Pattern para descomponer correctamente, Event-Driven Architecture para desacoplar servicios, y BFF Pattern para resolver el problema de los servicios parlanchines.