13. Buenas prácticas en microservicios

Los microservicios ofrecen autonomía y escalabilidad, pero solo aportan valor si se gobiernan con disciplina técnica y organizacional. Las buenas prácticas definen cómo dividir el dominio, cómo colaborar entre equipos y cómo automatizar los procesos para entregar software de calidad de forma sostenida. En este tema recopilamos principios esenciales que ayudan a consolidar una plataforma madura.

13.1 Mantener bajo acoplamiento y alta cohesión

Un microservicio debe resolver un conjunto acotado de responsabilidades alineadas con un contexto de dominio. La cohesión alta implica que sus clases y módulos persiguen un objetivo común, mientras que bajo acoplamiento significa que dependerá lo menos posible de detalles internos de otros servicios.

Para lograrlo se aplican prácticas como definir contratos explícitos, evitar compartir bases de datos, usar eventos para comunicar cambios de estado y mantener las dependencias en dirección hacia el dominio. La revisión periódica de límites de contexto y la observación de métricas de complejidad identifican servicios que necesitan reorganizarse.

13.1.1 Matriz sencilla de dependencias

La siguiente matriz permite visualizar dependencias y detectar acoplamientos no deseados.

class DependencyMatrix {

    private final Map<String, Set<String>> outgoing = new HashMap<>();

    void register(String service, String dependsOn) {
        outgoing.computeIfAbsent(service, key -> new HashSet<>()).add(dependsOn);
    }

    Set<String> findHighCoupling() {
        return outgoing.entrySet().stream()
                .filter(entry -> entry.getValue().size() > 5)
                .map(Map.Entry::getKey)
                .collect(Collectors.toSet());
    }
}

Automatizar la medición de dependencias facilita detectar servicios que están acumulando demasiadas integraciones y requieren refactorización.

13.2 Estándares de comunicación y versionado de APIs

Las APIs son el contrato fundamental entre servicios. Establecer estándares asegura consistencia y reduce errores de integración. El versionado clarifica la evolución de las operaciones y permite introducir cambios sin romper clientes existentes.

Las buenas prácticas incluyen definir nomenclatura de endpoints, uso consistente de códigos HTTP, manejo de errores mediante estructuras claras y documentar el versionado. Se recomienda adoptar un esquema como /v1, encabezados personalizados o versionado por contenido, según las necesidades. Cada versión debe contar con soporte y fecha de retiro definidas.

13.2.1 Filtro para enrutar versiones

El siguiente filtro aplica una estrategia de versionado por encabezado, derivando a la versión apropiada del servicio.

@Component
public class ApiVersionFilter implements HandlerFilterFunction<ServerResponse, ServerResponse> {

    @Override
    public Mono<ServerResponse> filter(ServerRequest request, HandlerFunction<ServerResponse> next) {
        String version = request.headers().firstHeader("X-Api-Version");
        if (!List.of("1", "2").contains(version)) {
            return ServerResponse.status(HttpStatus.BAD_REQUEST)
                    .bodyValue("Versión de API no soportada");
        }
        return next.handle(request.mutate().attribute("apiVersion", version).build());
    }
}

La gestión centralizada del versionado evita divergencias y facilita retirar versiones antiguas con comunicaciones claras.

13.3 Documentación y descubrimiento de servicios

Sin documentación adecuada, los equipos pierden tiempo interpretando contratos. La documentación debe describir la funcionalidad, los flujos de negocio, las APIs y los acuerdos de servicio (SLOs). Complementar con catálogos de servicios y mecanismos de descubrimiento simplifica el consumo y evita duplicidades.

Herramientas como Backstage, Swagger UI o portales personalizados permiten explorar servicios, revisar dependencias, consumir APIs y acceder a guías de despliegue. La documentación también incluye ejemplos de uso, esquemas de eventos y criterios de observabilidad.

13.3.1 Registro de servicios para catálogo interno

El siguiente ejemplo muestra cómo registrar metadatos que luego se publican en un catálogo consultable.

record ServiceMetadata(String name,
                       String ownerTeam,
                       URI docsUrl,
                       URI healthEndpoint,
                       List<String> domains) {}

class CatalogRegistry {

    private final List<ServiceMetadata> inventory = new CopyOnWriteArrayList<>();

    void register(ServiceMetadata metadata) {
        inventory.add(metadata);
    }

    List<ServiceMetadata> findByDomain(String domain) {
        return inventory.stream()
                .filter(item -> item.domains().contains(domain))
                .toList();
    }
}

Con un catálogo accesible, los equipos descubren rápidamente qué servicios existen, quién los mantiene y cómo monitorizarlos.

13.4 Automatización y cultura DevOps

La diversidad de servicios exige automatizar compilaciones, pruebas, despliegues, observabilidad y seguridad. La cultura DevOps promueve la colaboración entre desarrollo y operaciones, enfatiza la entrega continua y define responsabilidades compartidas sobre la confiabilidad.

Las buenas prácticas incluyen pipelines reproducibles, ambientes efímeros para pruebas, monitoreo centralizado y retroalimentación rápida ante incidentes. También se deben automatizar verificaciones de seguridad, rotación de secretos y cumplimiento de políticas.

13.4.1 Indicadores de madurez DevOps

El siguiente fragmento calcula indicadores inspirados en el reporte Accelerate para evaluar la madurez del proceso.

record DevOpsMetrics(Duration leadTime,
                    int deploymentsPerDay,
                    Duration meanTimeToRecovery,
                    double changeFailureRate) {}

class DevOpsScorecard {

    DevOpsLevel evaluate(DevOpsMetrics metrics) {
        if (metrics.leadTime().toHours() <= 24
                && metrics.deploymentsPerDay() >= 1
                && metrics.meanTimeToRecovery().toMinutes() <= 60
                && metrics.changeFailureRate() <= 0.15) {
            return DevOpsLevel.ELITE;
        }
        if (metrics.leadTime().toDays() <= 7
                && metrics.deploymentsPerDay() >= 1
                && metrics.meanTimeToRecovery().toHours() <= 24
                && metrics.changeFailureRate() <= 0.3) {
            return DevOpsLevel.ALTO;
        }
        return DevOpsLevel.BASICO;
    }
}

Monitorear estos indicadores orienta inversiones en automatización, testing y confiabilidad operativa.