Ir al contenido

Proyecto independiente de I+D · Colonia

Java y Spring

Tipos sellados, Bean Validation, Micrometer, Spring Modulith y ArchUnit, y los puntos donde una propiedad declarada deja de aplicarse.

No normativo

Versión del Companion
1.0
Corresponde a COADF Core
2.2
Estado
Vigente
Última revisión
Versión del perfil
1.0
Ejemplos de código
Los ejemplos de código llegarán en una revisión posterior.

Los ejemplos de código llegarán en una revisión posterior.

Propiedades de arquitectura tratadas

  • P-1Una jerarquía sellada que separa las propuestas de los valores verificados, validación en la frontera web y reglas de módulos que el build verifica.
  • P-4Micrometer Observation y Tracing para el contexto de ejecución, la identidad de auditoría en cada contrato y entradas de auditoría escritas dentro de la transacción de negocio.
  • P-5La procedencia como campos de los tipos de valor, serializada de forma explícita.
  • P-6Verificación de la arquitectura en la fase de pruebas, y checks obligatorios que no pueden pasar por haberse omitido.
  • P-7Puertos en el módulo de dominio y adaptadores en módulos propios.
  • P-8Una interfaz de políticas con un adaptador para el motor, y decisiones registradas con su revisión de la política.
  • P-2, P-3Solo con la profundidad que publica COADF.

Intención de arquitectura

La fuerza de Spring para estas propiedades es la declaración: restricciones sobre tipos, transacciones y validación aplicadas por el contenedor, observaciones configuradas una sola vez, reglas de módulos comprobadas por una prueba. Su fallo característico es la otra cara del mismo diseño. Una declaración se cumple en las rutas que el contenedor intercepta, a través de un proxy, en un binding web o en un executor que él configuró, y en ningún otro sitio. Una propiedad escrita como anotación es tan fuerte como el conjunto de llamadas que realmente pasan por Spring, y no más.

Este perfil hace corresponder cada propiedad con Spring Boot 4 y Spring Framework 7, la generación vigente cuando se escribió, y nombra los lugares donde una propiedad declarada deja de aplicarse sin hacer ruido.

Correspondencia tecnológica

Java y Spring: Correspondencia tecnológica
Propiedad de arquitecturaJava y Spring
Tipo de fronteraUna interfaz sealed con implementaciones record: un tipo de propuesta y un tipo verificado, construidos en módulos distintos. Los records son portadores de datos con inmutabilidad superficial: un componente que es una lista mutable sigue siendo mutable, así que hay que copiarlo en el constructor. Las clases selladas son una característica estándar desde Java 17.
Validación en tiempo de ejecuciónRestricciones de Jakarta Validation en los tipos de petición, activadas con @Valid. Una validación fallida de @RequestBody lanza MethodArgumentNotValidException, que por defecto se responde con 400.
Campos desconocidosUna decisión explícita y probada en los tipos de frontera sobre las propiedades que el tipo no declara, en lugar de lo que diga en ese momento la configuración del mapeador JSON.
Regla de arquitecturaApplicationModules.verify() de Spring Modulith, o reglas de ArchUnit escritas como pruebas unitarias.
Contexto de ejecuciónMicrometer Observation y Micrometer Tracing, con puente hacia OpenTelemetry.
Contexto asíncronoContextPropagatingTaskDecorator con la biblioteca context-propagation de Micrometer; para el executor autoconfigurado, la propiedad de Boot spring.task.execution.propagate-context.
Traza de auditoríaEntradas escritas en la transacción de negocio; permisos y triggers de base de datos como en el ejemplo de PostgreSQL; el registro de publicación de eventos de Spring Modulith para los eventos que no deben perderse.
Aislamiento de normasInterfaces de puerto en el módulo de dominio; un módulo adaptador por sistema externo; el cliente del proveedor nunca visible para el dominio.
PolíticaUna interfaz de políticas propiedad del dominio con un adaptador para Open Policy Agent, o tablas de reglas; la revisión de la política guardada en la decisión.
Pruebas de integraciónUn PostgreSQL real a través de Testcontainers, conectado con @ServiceConnection.

Patrón de referencia

Los ejemplos de código llegarán en una revisión posterior. Se aplazan hasta que los haya leído una persona revisora que trabaje en este ecosistema, en lugar de escribirlos para que los cuatro perfiles se parezcan. La estructura que sigue es el patrón de referencia que implementarán esos ejemplos.

Módulos

  • inference llama al modelo y devuelve un Proposal, un record que lleva el valor, el método y la procedencia. No depende de records ni de audit, y una verificación de Modulith o una regla de ArchUnit lo afirma en una prueba.
  • records es el único módulo que puede crear un VerifiedValue. El tipo no es público fuera del módulo, o es una clase cuyo constructor no lo es: un record público no puede ocultar su constructor, porque el constructor canónico de un record público tiene que ser público a su vez. La creación pasa por una factoría que exige la verificación de una persona con nombre.
  • audit añade entradas con los campos publicados, dentro de la transacción del cambio que registra, a través de un repositorio que no ofrece actualización ni borrado.
  • classification declara el puerto; classification.vendor lo implementa y es el único módulo que puede ver el cliente del proveedor o sus tipos.
  • policy declara la interfaz de políticas y el registro de decisión; un módulo adaptador habla con el motor.

Fronteras en tiempo de ejecución

La validación ocurre en el binding web, donde Spring evalúa las restricciones sobre el cuerpo de la petición, y de nuevo dentro de la factoría del dominio, porque el binding web es un punto de entrada entre varios: un listener de mensajes, un job por lotes y una prueba llegan todos al dominio sin pasar por él.

Las observaciones envuelven la frontera y las llamadas externas. Cada executor que ejecuta trabajo para una petición se configura para propagar el contexto, y la identidad de auditoría es un campo de cada mensaje y de cada argumento de job, nunca algo que se recupera a posteriori de un thread-local.

Modos de fallo

  1. La autoinvocación elude el proxy

    Spring AOP se basa en proxies: una llamada de un método de un bean a otro método del mismo bean no pasa por el proxy, así que ni la validación de métodos ni @Transactional se le aplican. Una restricción que se cumple para quien llama desde fuera del bean no se cumple para el propio bean, que es a menudo donde empieza la ruta por lotes.

  2. Restricciones declaradas, nunca evaluadas

    Las restricciones de Jakarta Validation sobre un tipo no hacen nada por sí solas. Se evalúan donde algo activa la validación: @Valid o @Validated en un binding web, o la validación de métodos a través del proxy. Un listener de mensajes que recibe el mismo tipo no valida nada salvo que lo pida.

  3. Propiedades no declaradas dejadas a la configuración del mapeador

    Que un documento JSON entrante pueda llevar propiedades que el tipo no declara es un ajuste del mapeador JSON. Para un tipo de frontera, la respuesta correcta suele ser rechazarlas, y esa decisión corresponde al tipo y a una prueba, no a una propiedad de la aplicación que otra persona puede cambiar.

  4. La inmutabilidad del ORM tomada por una salvaguarda de auditoría

    Hibernate ignora los cambios en memoria sobre una entidad gestionada @Immutable, sin actualización y sin excepción. Las actualizaciones masivas contra ella lanzan una excepción por defecto en Hibernate 7 y en 6.6 solo generaban un aviso. Nada de esto impide que un segundo cliente, un script o una migración actualicen la tabla; la protección corresponde a la base de datos.

  5. Eventos de auditoría perdidos después del commit

    Un evento que se procesa después del commit de la transacción se pierde si el handler falla, salvo que algo lo haya registrado. El registro de publicación de eventos de Spring Modulith escribe una entrada en la transacción que publica y conserva las publicaciones fallidas para volver a enviarlas; republicarlas automáticamente al reiniciar es un ajuste que hay que activar.

  6. Un record público como tipo verificado

    El constructor canónico de un record público tiene que ser público, así que cualquier código que pueda ver el tipo puede construir un valor verificado sin verificación. Hay que mantener el tipo fuera de su alcance, o usar una clase con constructor privado y una factoría.

  7. Dos puentes de tracing

  8. Pruebas de integración contra otra base de datos

    Los permisos, los triggers y el comportamiento de las restricciones son propiedades de la base de datos real. Las pruebas contra un sustituto embebido verifican el código y no la propiedad.

Verificación

  • Prueba unitaria

    Pasa cuando: El tipo verificado solo puede crearse a través de la factoría, y solo con la verificación de una persona con nombre; un rechazo deja el atributo vacío.

    Prueba de dientes: Hacer público el tipo verificado en una rama desechable: falla una prueba que afirma que no es accesible desde el módulo de inferencia.

  • Prueba de arquitectura

    Pasa cuando: La verificación de Spring Modulith pasa, o pasan las reglas de ArchUnit: ninguna dependencia de inference hacia records o audit, ningún tipo del proveedor en el dominio. ArchUnit comprueba las dependencias entre paquetes y clases como pruebas unitarias corrientes, y la verificación de Modulith rechaza los ciclos entre módulos y las referencias a los paquetes internos de otro módulo, salvo a módulos declarados abiertos. Cualquiera de las dos herramientas comprueba las reglas que se han escrito; que sean las reglas correctas depende del diseño del equipo.

    Prueba de dientes: Introducir a propósito la dependencia prohibida: la verificación falla.

  • Prueba de integración

    Pasa cuando: Contra PostgreSQL desde Testcontainers: el rol de la aplicación no puede actualizar ni borrar la traza, y las entradas se escriben en la misma transacción que el cambio.

    Prueba de dientes: Deshacer la transacción de negocio después de escribir: la entrada de auditoría no debe sobrevivir.

  • Prueba de integración

    Pasa cuando: El contexto sobrevive a los executors que usa la aplicación: un exportador en memoria muestra el trabajo asíncrono como parte de la misma traza, y la identidad de auditoría figura en su registro.

    Prueba de dientes: Quitar el task decorator: la prueba falla.

  • Prueba de contrato

    Pasa cuando: Productor y consumidor coinciden en los contratos de propuesta y de decisión, con la procedencia y la revisión de la política como campos obligatorios.

  • Prueba de extremo a extremo

    Pasa cuando: Una salida cuyo atributo no se ha verificado no puede publicarse, sea cual sea el punto de entrada por el que llegó la petición.

Realizaciones alternativas

  • Otros frameworks Java (Quarkus, Micronaut, Jakarta EE) expresan la misma frontera con modelos de intercepción distintos; la pregunta de la autoinvocación hay que plantearla en cada uno.
  • SQL explícito (jOOQ o JDBC directo) para la traza de auditoría en lugar de un ORM, donde las sentencias exactas importan más que la comodidad del mapeo.
  • Frameworks de event sourcing donde el historial es el modelo.
  • Tablas de reglas propiedad de la aplicación en lugar de un motor de políticas externo, con la misma exigencia de registrar la revisión de la política.

Compromisos

  • Lo declarativo es conciso e invisible. Quien lee no puede ver desde el punto de llamada si se aplica una restricción o una transacción; tienen que mostrarlo las pruebas.
  • Spring Modulith codifica convenciones, lo que lo hace rápido de adoptar y con opiniones propias; ArchUnit es general y exige escribir las reglas.
  • JPA es cómodo y pone una capa entre el código y las protecciones propias de la base de datos. Para la traza de auditoría, esa capa es la parte a través de la cual hay que ver.
  • Las fronteras de módulo dentro de un único desplegable mantienen sencillo el modelo operativo, y hacen que la frontera dependa por completo de la verificación del build.

Limitaciones

  • En Companion 1.0 no se publica código para este perfil, y no se ejecutó ningún ejemplo. El comportamiento descrito procede de la documentación oficial vigente, comprobada el 11 de septiembre de 2026, para Spring Boot 4.1, Spring Framework 7.0, Spring Modulith 2.1, Hibernate ORM 7.4 y Java SE 25.
  • Nada de lo que aquí figura muestra cómo se representa la confianza, cuándo se requiere revisión ni cómo se organiza la revisión; COADF no publica esas partes de P-2 y P-3.
  • Todo lo citado es comportamiento del lenguaje o del framework. La propiedad de arquitectura, que una propuesta no pueda convertirse en registro sin una verificación, es un diseño que el equipo aún tiene que hacer y probar: ninguna anotación, record ni regla de módulos la proporciona por sí sola.

Lo que este perfil no establece

Seguir este perfil no establece cumplimiento normativo, certificación ni evaluación de la conformidad, y COADF no exige Spring.

Entorno de referencia probado

No se ejecutó ningún ejemplo para este perfil.

Fuentes

COADF Engineering Companion 1.0 · no normativo · corresponde a COADF Core 2.2

Derechos de publicación reservados. Por ahora no se concede ninguna licencia pública para el COADF Engineering Companion 1.0 ni para sus ejemplos de referencia.

Estado de propiedad intelectual y de publicación