01 - Fundamentos de Spring Modulith

Estos apuntes resumen los conceptos fundamentales de Spring Modulith 2.1.1. Spring Modulith ayuda a organizar una aplicación Spring Boot en módulos funcionales, definir cómo colaboran y verificar sus límites.

Un módulo de aplicación es una unidad funcional que puede contener:

  • Una API ofrecida a otros módulos, formada por componentes Spring expuestos y eventos publicados.
  • Una implementación interna que los otros módulos no deben utilizar directamente.
  • Dependencias hacia APIs de otros módulos, eventos que escucha y configuración que consume.

La API ofrecida es la interfaz proporcionada (*provided interface*); las dependencias que el módulo necesita son sus interfaces requeridas (*required interfaces*).

Spring Modulith puede deducir el modelo de módulos del código y su organización en paquetes:

ApplicationModules modules = ApplicationModules.of(Application.class);
modules.forEach(System.out::println);

El tipo ApplicationModules representa en memoria los módulos detectados, sus componentes y la visibilidad observada.

Por defecto, el paquete que contiene la clase principal anotada con @SpringBootApplication es el paquete raíz. Cada paquete hijo directo se considera un módulo.

Si el paquete del módulo no tiene subpaquetes, se trata como módulo simple. Todos sus tipos públicos forman parte de la API del módulo; se pueden mantener tipos internos con visibilidad de paquete de Java.

com.example
├── Application.java
├── orders                 (módulo)
│   ├── OrderManagement.java
│   └── InternalPolicy.java
└── stock                  (módulo)
    └── StockManagement.java

Si un módulo contiene subpaquetes, Spring Modulith considera que el paquete raíz del módulo es su API. Los subpaquetes son internos y otros módulos no deben acceder a ellos.

com.example.orders             (API del módulo)
├── OrderManagement.java
└── internal                    (implementación interna)
    └── OrderPolicy.java

Los tipos internos pueden necesitar ser public para que otras clases del mismo módulo los usen. El compilador Java no impide por sí solo que otro módulo acceda a esos tipos públicos; la verificación de Spring Modulith permite detectar estas dependencias no permitidas.

Desde Spring Modulith 1.3, un módulo puede contener módulos anidados. Se declaran anotando el paquete que debe constituir el módulo anidado con @ApplicationModule.

com.example.inventory
├── InventoryManagement.java
└── nested                  (módulo anidado: @ApplicationModule)
    ├── NestedApi.java
    └── internal
        └── NestedInternal.java

Reglas de acceso principales:

  • La API del módulo anidado puede utilizarse desde el módulo padre y desde los módulos hermanos anidados autorizados.
  • El módulo anidado puede acceder a tipos de su módulo padre, incluso a tipos internos.
  • Los módulos de nivel superior pueden acceder a la API expuesta por el módulo anidado, no a su implementación interna.

Un módulo puede declararse abierto con @ApplicationModule(type = Type.OPEN) en su package-info.java.

@org.springframework.modulith.ApplicationModule(
    type = Type.OPEN
)
package com.example.inventory;

En un módulo abierto, los tipos internos también pueden ser accedidos desde otros módulos y se incorporan a la interfaz sin nombre, salvo que se asignen a una interfaz con nombre.

Este modo facilita modularizar gradualmente una aplicación existente. En una aplicación diseñada modularmente desde el principio, abrir módulos suele indicar que los límites o la estructura de paquetes pueden mejorarse.

Un módulo puede declarar explícitamente los otros módulos de los que se permite depender mediante allowedDependencies:

@org.springframework.modulith.ApplicationModule(
    allowedDependencies = "orders"
)
package com.example.inventory;

En este ejemplo, el módulo inventory puede depender de orders y de tipos que no pertenezcan a ningún módulo. La verificación de la estructura comprueba que el código respeta las dependencias declaradas.

Por defecto, la API de un módulo con subpaquetes es su paquete raíz. Para exponer otro paquete concreto se declara una interfaz con nombre (*named interface*) mediante @NamedInterface en el package-info.java del paquete:

@org.springframework.modulith.NamedInterface("spi")
package com.example.orders.spi;

El paquete expuesto puede incluirse de forma explícita en las dependencias permitidas:

@org.springframework.modulith.ApplicationModule(
    allowedDependencies = "orders :: spi"
)
package com.example.inventory;

El separador :: combina el nombre del módulo con el de su interfaz. También se puede permitir el acceso a todas las interfaces con nombre del módulo usando orders :: *.

La anotación @Modulithic de la clase principal permite configurar el modelo global:

Atributo Función
systemName Nombre legible de la aplicación en la documentación generada
sharedModules Módulos que se incluyen siempre en las pruebas de integración de módulos
additionalPackages Paquetes raíz adicionales en los que detectar módulos

La estrategia predeterminada busca módulos en paquetes hijos directos del paquete raíz. Se puede exigir que los módulos estén anotados con @ApplicationModule (o con la anotación @Module de jMolecules) mediante:

spring.modulith.detection-strategy=explicitly-annotated

Si ninguna estrategia integrada se ajusta a la aplicación, se puede implementar ApplicationModuleDetectionStrategy. Esta recibe el paquete raíz y devuelve los paquetes que deben considerarse módulos.

Los módulos también pueden aportar paquetes raíz externos mediante ApplicationModuleSourceFactory, registrada en META-INF/spring.factories. La estrategia de detección configurada se aplica a esos paquetes.

Una estrategia ApplicationModuleDetectionStrategy también puede personalizar la detección de interfaces con nombre. Por ejemplo, se pueden exponer automáticamente los subpaquetes llamados api:

@Override
NamedInterfaces detectNamedInterfaces(
        JavaPackage basePackage, ApplicationModuleInformation information) {
    return NamedInterfaces.builder()
            .recursive()
            .matching("api")
            .build();
}

La API de construcción permite seleccionar o excluir paquetes. El paquete raíz del módulo sigue formando parte de su interfaz sin nombre.

Si la estrategia personalizada se necesita también en funcionalidades de ejecución, como inicializadores o funciones de producción, spring-modulith-core debe estar disponible como dependencia de compilación, no solo en las pruebas.

La estructura de paquetes expresa límites de módulos; las interfaces expuestas definen sus contratos. Spring Modulith permite inspeccionar y verificar que las dependencias respetan esos límites.
  • clase/spring/modulith/fundamentos.txt
  • Última modificación: 2026/10/04 09:54
  • por cesguiro