Alejandría

Busca en Alejandría

Técnicas

Architecture fitness functions

Convierte características de arquitectura en comprobaciones automáticas y continuas.

01

En pocas palabras

Mecanismos objetivos (pruebas, métricas, reglas) que evalúan si el sistema sigue cumpliendo una característica de arquitectura deseada a medida que evoluciona.

Qué problema resuelve

Decisiones de arquitectura que se erosionan en silencio: dependencias prohibidas, latencias que crecen, ciclos entre módulos.

Cuándo utilizarlo

Sistemas que evolucionan con muchos equipos. Ejemplo: impedir que el módulo de dominio dependa de la capa web o que una API supere 300 ms de p95.

02

De la idea a la práctica

Funcionamiento, ejemplos y decisiones para pasar al siguiente nivel.

Tipos

Dimensión Opciones
Alcance Atómica (un aspecto aislado) u holística (varios combinados)
Momento Disparada (en CI, al hacer merge) o continua (en producción)
Resultado Estática (pasa/no pasa) o dinámica (umbral que depende del contexto)
Automatización Automática o manual (p. ej. revisión legal), pero explícita

Ejemplos por característica

Característica Fitness function
Modularidad ArchUnit: el dominio no depende de infraestructura; sin ciclos
Rendimiento Prueba de carga en CI: p95 < 300 ms con 200 RPS
Seguridad Ninguna dependencia con CVE crítico (SBOM + escáner)
Disponibilidad SLO de 99,9 % con alerta por consumo de error budget
Coste Coste por transacción no supera el umbral mensual
Evolución de API Pruebas de contrato que detectan cambios incompatibles (Pact)

Ejemplo con ArchUnit

@AnalyzeClasses(packages = "com.krediya.originacion")
class ArquitecturaTest {

    @ArchTest
    static final ArchRule capas = layeredArchitecture()
        .consideringAllDependencies()
        .layer("Web").definedBy("..web..")
        .layer("Aplicacion").definedBy("..aplicacion..")
        .layer("Dominio").definedBy("..dominio..")
        .layer("Infraestructura").definedBy("..infraestructura..")
        .whereLayer("Web").mayNotBeAccessedByAnyLayer()
        .whereLayer("Aplicacion").mayOnlyBeAccessedByLayers("Web")
        .whereLayer("Dominio").mayOnlyBeAccessedByLayers("Aplicacion", "Infraestructura");

    @ArchTest
    static final ArchRule sinCiclos = slices()
        .matching("com.krediya.originacion.(*)..")
        .should().beFreeOfCycles();
}

Se ejecuta como una prueba más con JUnit 5; si alguien importa un repositorio JPA desde un controlador, el build falla con un mensaje explícito. Equivalentes: dependency-cruiser (JavaScript/TypeScript), import-linter (Python), NetArchTest (.NET).

Antipatrones frecuentes

  • Reglas que fallan siempre y se acaban ignorando o desactivando.
  • Medir lo fácil (cobertura) en lugar de lo importante (acoplamiento, latencia).
  • Fitness functions sin dueño: cuando falla una, nadie sabe si cambiar el código o la regla.

Equivalente en TypeScript con dependency-cruiser

// .dependency-cruiser.cjs
module.exports = {
  forbidden: [
    {
      name: 'dominio-sin-infraestructura',
      severity: 'error',
      from: { path: '^src/dominio' },
      to: { path: '^src/(infraestructura|web)' },
    },
    { name: 'sin-ciclos', severity: 'error', from: {}, to: { circular: true } },
    {
      name: 'apps-no-importan-otras-apps',
      severity: 'error',
      from: { path: '^apps/([^/]+)/' },
      to: { path: '^apps/', pathNot: '^apps/$1/' },
    },
  ],
};
npx depcruise src --config .dependency-cruiser.cjs

Fitness functions en producción

Característica Función continua
Disponibilidad Alertas de burn rate sobre el SLO
Latencia Monitorización sintética de los recorridos críticos cada minuto
Resiliencia Experimentos de caos programados con resultado esperado
Seguridad Escaneo continuo de configuración de nube (CSPM)
Coste Alerta si el coste por transacción supera el umbral
Cumplimiento Políticas como código evaluadas sobre el estado real (continuous compliance)

Catálogo de fitness functions

Nombre Característica Tipo Umbral Dónde Dueño
capas-dominio Modularidad Atómica, disparada 0 violaciones PR Equipo
p95-api-solicitudes Rendimiento Atómica, disparada < 300 ms a 200 RPS Nocturno Equipo
cve-criticos Seguridad Atómica, disparada 0 críticos sin VEX PR y diario Seguridad
slo-disponibilidad Disponibilidad Holística, continua 99,9 % en 28 días Producción Equipo + SRE
coste-por-solicitud Coste Holística, continua < 0,02 USD Mensual Plataforma

Un catálogo visible ayuda a la gerencia a ver qué decisiones de arquitectura están protegidas y cuáles dependen solo de la buena voluntad.

Introducir una regla en un código existente

  1. Ejecuta la regla y registra las violaciones actuales como línea base (ArchUnit ofrece freezing de violaciones).
  2. Haz fallar el build solo con violaciones nuevas.
  3. Reduce la línea base de forma incremental en cada iteración.

Criterios de implementación

Deriva cada función de un atributo de calidad priorizado y enlázala desde el ADR que la motivó. Ejecuta las atómicas en cada pull request y las holísticas en un pipeline nocturno o en producción. Revisa los umbrales cuando cambie el contexto, dejando constancia de por qué.

Continúa en las fuentes

Documentación oficial para verificar detalles y explorar más.