Skip to content

feat: let controllers use the framework-free Nova libraries - #5

Merged
ahincho merged 4 commits into
mainfrom
feat/allow-nova-libraries-in-controllers
Oct 2, 2026
Merged

ahincho merged 4 commits into
mainfrom
feat/allow-nova-libraries-in-controllers

Conversation

@ahincho

@ahincho ahincho commented Oct 2, 2026

Copy link
Copy Markdown
Owner

Qué cambia

Un controlador no podía lanzar ApplicationError.invalidInput(...), de los errores por capas de ADR-031: la regla controllers_depend_only_on_allowed_layers solo admitía las capas del servicio, java, jakarta, Spring, Quarkus y las librerías de prueba, y marcaba la llamada.

  • La regla del controlador admite pe.edu.nova.java.libs.., las librerías de Nova sin framework. Es más estrecho que pe.edu.nova.. a propósito: un servicio también vive bajo pe.edu.nova, y admitir el prefijo entero dejaría sin efecto la regla para los paquetes propios del servicio. Los starters (pe.edu.nova.java.starters..) siguen fuera, porque cablean el framework.
  • Las demás capas no cambian, y es una decisión. El controlador era la única capa con una lista de permitidos. Las reglas de entity, service, repository y dto solo prohíben capas concretas, así que ya admitían esas librerías: el dominio ya podía lanzar DomainError, y un servicio puede declarar throws de un error de Nova sin que services_should_not_throw_generic_exception lo marque. Las pruebas lo demuestran.
  • Una capa sin clases sigue fallando con failed to check any classes, y no se relaja. Es la misma comprobación que detecta un basePackage() que dejó de coincidir con el código tras un renombrado, o una capa llamada controllers en vez de controller, casos que de otro modo pasarían sin revisar ninguna clase. Relajarla quitaría esa red a todos los servicios para ahorrarle a unos pocos una clase de relleno. Un servicio que de verdad no tiene la capa puede apagar la comprobación con archRule.failOnEmptyShould=false en archunit.properties, que es el interruptor de ArchUnit; el README lo explica.
  • README. Muestra Gradle (la instalación desde GitHub Packages y el plugin del toolchain que trae la librería), ./gradlew test y la versión actual, y describe las reglas tal como son. La lista anterior prometía una regla contra @Autowired que la librería no tiene: lo que existe es que los campos de instancia de un servicio sean final.
  • La versión del README la mantiene release-please (extra-files y marcas de bloque), para que no vuelva a quedarse en 1.0.0.
  • Pruebas nuevas. LayeredArchitectureRulesTest corre las reglas con el motor de ArchUnit (EngineTestKit) sobre tres servicios de ejemplo en src/test/java/.../fixtures: uno que cumple todo, uno cuyo controlador se sale de las capas y uno sin repositorio, entidad ni DTO. Gradle no los corre como pruebas, porque dos incumplen reglas a propósito.

Es un feat: y no un cambio incompatible: la lista de permitidos solo crece, así que nada de lo que pasaba deja de pasar. Con él sale la 1.2.0.

Cómo se verificó

  • Rojo y verde. Con las pruebas nuevas y el código principal sin cambiar, fallan dos (aServiceThatFollowsTheLayersPassesEveryRule y aControllerMayUseTheNovaLibrariesButNotTheRestOfNova) con la violación calls method <...FixtureError.of(java.lang.String)> in (ItemController.java:30), la misma que vería el template con ApplicationError. Con el cambio pasan las seis pruebas del repositorio.
  • ./gradlew build javadoc: verde, con los mismos avisos de Checkstyle (9) y de Javadoc (1) que tenía main; ninguno es nuevo.
  • Las mismas pruebas pasan con ArchUnit 1.5.1, la versión que el toolchain fija para los servicios.
  • Sin exclude("**/fixtures/**") el build falla con 10 pruebas rotas, así que la exclusión hace falta.
  • El interruptor archRule.failOnEmptyShould=false del README se probó: con él, el servicio sin repositorio, entidad ni DTO deja de fallar.

Run the rules of LayeredArchitectureTest through the ArchUnit engine, with
the EngineTestKit of JUnit, over three small sample services kept under
fixtures: one that follows the layers, one whose controller reaches outside
them and one that only has a controller and a service.

The samples pin what the rules do today. The entity, service and repository
layers can already use the framework-free Nova libraries, a controller
cannot reach other packages of the service or the Nova starters, and a layer
with no classes fails instead of passing.

Gradle does not run the samples as tests, because some break the rules on
purpose. Only LayeredArchitectureRulesTest runs them.
The controller rule only admitted the layers, java, jakarta, Spring, Quarkus
and the test libraries, so a controller could not throw
ApplicationError.invalidInput(...) from the layered errors of ADR-031: the
rule flagged the call.

Allow pe.edu.nova.java.libs.., the framework-free Nova libraries, in that
rule. It is narrower than pe.edu.nova.. on purpose. A service lives under
pe.edu.nova too, so allowing the whole prefix would stop the rule from
catching a controller that reaches the service's own packages outside the
layers. The Nova starters stay out, because they wire the framework.

The controller was the only layer with an allow-list. The rules of the
entity, service, repository and DTO layers only forbid other layers, so they
already accepted these libraries and the domain could already throw
DomainError. The sample services now prove it.
The README still showed Maven, version 1.0.0 and mvn test, and it listed a
rule the library does not have. The library forbids service fields that are
not final, which rules out field injection, but it never looks for
@Autowired.

Show the Gradle install from GitHub Packages and the toolchain plugin that
brings the library, run the tests with Gradle, and list each layer with the
rules that apply to it. Explain that the controller is the only layer with
an allow-list and why it admits pe.edu.nova.java.libs.. and not
pe.edu.nova.., and that a layer with no classes fails on purpose, with the
ArchUnit switch for a service that really lacks one.
The install snippet of the README names the version of the library, and it
went stale after the first release: it still showed 1.0.0 when the library was
at 1.1.2. Mark the snippet for release-please, so that each release pull
request updates it together with the changelog.
@ahincho ahincho self-assigned this Oct 2, 2026
@ahincho
ahincho merged commit d8c1bf5 into main Oct 2, 2026
7 checks passed
@ahincho
ahincho deleted the feat/allow-nova-libraries-in-controllers branch October 2, 2026 03:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant