11. Desafíos y problemas comunes

Los microservicios prometen escalabilidad, despliegue independiente y flexibilidad tecnológica. Sin embargo, al fragmentar la solución aparecen riesgos operativos y de diseño que pueden superar los beneficios previstos. Comprender estos desafíos ayuda a planificar mitigaciones y a construir plataformas resilientes.

11.1 Complejidad operativa y de despliegue

Pasar de un monolito a decenas de servicios incrementa la cantidad de artefactos, configuraciones, pipelines y dependencias que deben administrarse. Cada servicio requiere observabilidad, seguridad, escalado y desplegable propio. Sin una automatización completa surgen cuellos de botella y configuraciones divergentes.

Para contener la complejidad es indispensable adoptar infraestructura como código, catálogos de servicios, plantillas reutilizables y prácticas DevOps como Site Reliability Engineering. Los equipos deben compartir estándares de logging, métricas y políticas de seguridad. Además, se necesita una gobernanza liviana que valide cambios y gestione versiones globales.

11.1.1 Tablero de seguimiento de despliegues

Un tablero central permite visualizar la salud de los pipelines y detectar cuellos de botella. El siguiente modelo se concentra en el estado de cada servicio.

public class DeploymentDashboard {

    private final Map<String, DeploymentStatus> deployments = new ConcurrentHashMap<>();

    public void register(String service, DeploymentStatus status) {
        deployments.put(service, status);
    }

    public List<DeploymentStatus> findDelayed() {
        return deployments.values().stream()
                .filter(status -> status.stage() == Stage.PROMOTION && status.inProgressSince().isBefore(Instant.now().minus(Duration.ofHours(2))))
                .sorted(Comparator.comparing(DeploymentStatus::inProgressSince))
                .toList();
    }
}

Monitorear el estado de despliegues ayuda a detectar servicios bloqueados, tareas manuales recurrentes y dependencias excesivas.

11.2 Sobrecarga de comunicación y latencia

La comunicación remota introduce latencia y potenciales fallas. Una función antes local termina implicando mensajes a varios servicios, lo que incrementa el tiempo de respuesta y el consumo de ancho de banda. Sin diseño cuidadoso, los servicios pueden convertirse en chatty, intercambiando mensajes pequeños en secuencia.

Mitigar la latencia requiere diseñar APIs orientadas a casos de uso, aplicar caché y evitar llamadas en cadena innecesarias. También es recomendable instrumentar los servicios con trazas distribuidas para identificar los saltos que generan mayor tiempo. El uso de agregadores, bulkheads y circuit breakers disminuye la propagación de fallas.

11.2.1 Perfilamiento de latencia

El siguiente fragmento recolecta tiempos promedio y percentiles para cada endpoint, permitiendo descubrir puntos de alta latencia.

@Component
public class LatencyProfiler {

    private final Map<String, Histogram> histograms = new ConcurrentHashMap<>();

    public void record(String endpoint, long durationMillis) {
        histograms.computeIfAbsent(endpoint, key -> new Histogram(5)).recordValue(durationMillis);
    }

    public Snapshot snapshot(String endpoint) {
        Histogram histogram = histograms.getOrDefault(endpoint, new Histogram(5));
        return new Snapshot(histogram.getMean(), histogram.getValueAtPercentile(95));
    }
}

Con información de latencias se pueden ajustar tiempos de espera, reducir secuencias de llamadas y priorizar optimizaciones.

11.3 Dificultad para mantener consistencia

La distribución de datos entre microservicios complica mantener consistencia inmediata. Cada servicio controla su propia base de datos y la sincronización ocurre a través de eventos o APIs. Los retrasos, fallas en la mensajería o reintentos pueden crear estados intermedios que los usuarios perciben como errores.

Las mitigaciones incluyen diseñar flujos idempotentes, incluir identificadores de correlación, detectar eventos duplicados y monitorear la edad de los mensajes. Además, es clave definir expectativas claras a los usuarios (consistencia eventual) y ofrecer mecanismos de conciliación automática en caso de divergencias.

11.3.1 Verificador de consistencia eventual

El siguiente servicio identifica registros desincronizados entre bases propietarias y devuelve un reporte para iniciar acciones correctivas.

@Service
public class ConsistencyChecker {

    private final OrdersRepository ordersRepository;
    private final BillingRepository billingRepository;

    public ConsistencyChecker(OrdersRepository ordersRepository, BillingRepository billingRepository) {
        this.ordersRepository = ordersRepository;
        this.billingRepository = billingRepository;
    }

    public List<Inconsistency> findInconsistencies(Instant threshold) {
        List<Order> delayedOrders = ordersRepository.findAllConfirmedAfter(threshold);
        return delayedOrders.stream()
                .filter(order -> billingRepository.findInvoiceByOrderId(order.id()).isEmpty())
                .map(order -> new Inconsistency(order.id(), order.confirmedAt(), "Factura ausente"))
                .toList();
    }
}

Herramientas de conciliación como esta permiten detectar divergencias antes de que impacten masivamente en los usuarios.

11.4 Cómo evitar caer en el monolito distribuido

El riesgo más frecuente es recrear los vicios del monolito, pero repartidos en la red. Dependencias cruzadas, contratos poco claros y bases de datos compartidas provocan un monolito distribuido que combina lo peor de ambos mundos: complejidad operativa y falta de autonomía.

Para evitarlo, cada servicio debe tener un dominio bien definido, contratos versionados y propiedad total de sus datos. Las integraciones se realizan mediante APIs y eventos diseñados con intención, los equipos son responsables del ciclo de vida end-to-end y se aplican revisiones arquitectónicas regulares. El uso de Domain-Driven Design y la identificación de límites de contexto ayudan a mantener cohesión.

11.4.1 Lista de comprobación para detectar monolitos distribuidos

  • ¿Existe una base de datos compartida entre varios servicios que requiere coordinación manual?
  • ¿Un cambio simple obliga a desplegar múltiples servicios de forma sincronizada?
  • ¿Los equipos no pueden decidir tecnológicamente sobre su servicio sin pedir autorización externa?
  • ¿Las APIs exponen modelos genéricos sin reflejar el lenguaje de ubicación del dominio?
  • ¿La mayoría de los errores proviene de fallas de comunicación por contratos difusos?

Responder afirmativamente a estas preguntas indica la necesidad de redefinir límites, reforzar la propiedad de datos y reducir acoplamientos.