====== 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.
===== 1. Módulos de aplicación =====
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.
===== 2. Organización simple =====
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
===== 3. Módulos con paquetes internos =====
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.
===== 4. Módulos anidados =====
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.
===== 5. Módulos abiertos =====
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.
===== 6. Declarar dependencias permitidas =====
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.
===== 7. Interfaces con nombre =====
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 :: *''.
===== 8. Personalizar el modelo de módulos =====
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 |
==== 8.1 Detección de 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.
==== 8.2 Detección de interfaces con nombre ====
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.
===== Referencia =====
* [Documentación oficial de Spring Modulith: Fundamentals](https://docs.spring.io/spring-modulith/reference/fundamentals.html) (versión 2.1.1).