====== 04.2 - Casos de uso y colaboración entre módulos ======
Un **caso de uso** coordina una operación completa de la aplicación: obtiene las entradas, invoca los servicios necesarios y organiza el resultado. Expresa un flujo de aplicación; no debe contener las reglas internas de los modelos ni sustituir a los servicios de dominio.
===== 1. Responsabilidad del caso de uso =====
El caso de uso coordina servicios y dependencias necesarias para completar una operación. No invoca otro caso de uso: cada caso de uso representa un punto de entrada independiente al comportamiento de aplicación.
@startuml
class Controller
class "CancelOrder" as UseCase
class OrderService
Controller --> UseCase : invoca
UseCase --> OrderService : coordina
@enduml
Invocar un caso de uso desde otro acopla flujos que deberían poder componerse y reutilizarse de forma independiente. Si varios flujos necesitan una misma regla, se comparte mediante un servicio de dominio, no encadenando casos de uso.
===== 2. Servicio o caso de uso: ¿quién coordina? =====
Una colaboración pertenece a un **servicio** cuando forma parte de su comportamiento de dominio de manera consistente. Si depende del flujo concreto que se está ejecutando, la coordinación corresponde al **caso de uso**. Los servicios pueden colaborar con otros servicios; si necesitan otro módulo, usan su API pública.
^ Situación ^ Responsable de coordinar ^
| Cada cancelación debe liberar la reserva de stock | El servicio de pedidos colabora con la API pública de stock en cada cancelación |
| Solo un flujo específico envía una notificación adicional al cancelar | El caso de uso de ese flujo invoca la API pública correspondiente |
El criterio de “siempre” ayuda a distinguir el comportamiento estable de un servicio de una decisión propia de un flujo, pero no es una regla mecánica: las invariantes del dominio deben quedar protegidas aunque un flujo sea su único consumidor. Toda colaboración entre módulos cruza el límite mediante la API pública del módulo requerido, no mediante sus servicios privados.
===== 3. API pública y contratos entre módulos =====
Cuando un módulo necesita capacidades de otro, utiliza su **API pública interna**. No accede directamente a servicios privados ni a modelos internos del módulo proveedor.
@startuml
package "Módulo de pedidos" {
class CancelOrderUseCase
class OrderService
}
package "Módulo de stock" {
interface StockModuleApi
class StockService
class StockModel
}
CancelOrderUseCase --> OrderService : coordina
OrderService --> StockModuleApi : siempre libera la reserva
CancelOrderUseCase ..> StockModuleApi : este flujo solicita otra operación
StockModuleApi --> StockService : delega internamente
StockService --> StockModel
@enduml
La **API pública interna** define un contrato entre módulos. Puede ser una interfaz Java y sus DTO; no implica una llamada HTTP ni convierte el monolito modular en una aplicación distribuida.
===== 4. DTO y mapeo entre límites =====
Cuando se usan modelos de dominio **enriquecidos**, no conviene exponerlos directamente al cruzar límites entre capas o módulos. Se pueden intercambiar **DTO** que expresen los datos necesarios para cada contrato; dentro del módulo, los modelos conservan su comportamiento e invariantes. Es una decisión de diseño, no un requisito de todos los estilos de dominio.
**MapStruct** genera implementaciones de mapeadores entre DTO y modelos. El mapeo transforma datos; las decisiones de negocio siguen perteneciendo a servicios y modelos.
La construcción e inyección de los mapeadores generados se resuelve en la composición de la aplicación, sin añadir dependencias de Spring al dominio.
@Mapper
public interface StockMapper {
StockRequestDto toDto(StockRequest request);
StockResult toModel(StockResultDto dto);
}
La interfaz pública y sus DTO pertenecen al contrato que ofrece el módulo. Los tipos internos no deben filtrarse a través de ese contrato.
===== 5. Pruebas de casos de uso =====
Probar el caso de uso como coordinador:
* Verificar qué servicios y APIs públicas invoca para cada flujo.
* Comprobar los datos enviados y cómo se gestiona el resultado o error.
* Simular servicios y APIs de otros módulos para aislar la coordinación.
* Probar aparte las reglas de los servicios, los modelos y los mapeadores relevantes.
No invocar la implementación completa de otros módulos en cada prueba del caso de uso: se comprueba el contrato de colaboración, mientras que la API pública y sus implementaciones tienen sus propias pruebas.
@Test
void notifiesStockWhenThisFlowCancelsOrder() {
when(orderService.cancel(42L)).thenReturn(cancelledOrder);
cancelOrder.execute(42L);
verify(stockModuleApi).releaseReservation(
new ReleaseReservationRequest(42L));
}
Los casos de uso coordinan flujos y colaboran con servicios o APIs públicas de módulos. No deben invocar otros casos de uso; las reglas compartidas pertenecen a servicios o modelos de dominio.