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.
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.
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.
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.
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.
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.
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.
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.
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.