====== 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).