|
| 1 | +# ADR-0069: Estrategia de Herencia en Domain Layer - Clase Base AggregateRoot |
| 2 | + |
| 3 | +**Estado:** Aceptado |
| 4 | +**Fecha:** 2026-05-29 |
| 5 | +**Decisores:** Equipo de Arquitectura |
| 6 | +**Supersede:** ADR-0054 (Aislamiento de Shell Libraries) |
| 7 | + |
| 8 | +--- |
| 9 | + |
| 10 | +## Contexto |
| 11 | + |
| 12 | +El Domain layer de UMS (`Ums.Domain`) actualmente hereda de clases base `AggregateRoot<T>` y `Entity<T>` proporcionadas por `Ums.Shell.Ddd`. Esto crea una dependencia transitiva hacia **MediatR** (v12.3.0) a través de la librería shell. |
| 13 | + |
| 14 | +### Arquitectura Actual |
| 15 | + |
| 16 | +``` |
| 17 | +Ums.Domain (Proyecto) |
| 18 | + └── ProjectReference → Ums.Shell.Ddd |
| 19 | + └── PackageReference → MediatR (12.3.0) |
| 20 | +``` |
| 21 | + |
| 22 | +### Código Relevante |
| 23 | + |
| 24 | +**Ejemplo de Entity del Domain** (`src/apps/ums.api/Ums.Domain/Authorization/Profile/Profile.cs`): |
| 25 | +```csharp |
| 26 | +public sealed class Profile : AggregateRoot<Profile, ProfileProps> |
| 27 | +{ |
| 28 | + // Hereda: Id, BrokenRules, IsValid(), DomainEvents |
| 29 | +} |
| 30 | +``` |
| 31 | + |
| 32 | +**Clase Base del Shell** (`libs/shell/ddd/src/Ums.Shell.Ddd/`): |
| 33 | +```csharp |
| 34 | +public abstract class AggregateRoot<T, TProps> : Entity<T, TProps> |
| 35 | + where TProps : Props |
| 36 | +{ |
| 37 | + public IDomainEvents DomainEvents { get; } |
| 38 | + // ... Integración con MediatR |
| 39 | +} |
| 40 | +``` |
| 41 | + |
| 42 | +--- |
| 43 | + |
| 44 | +## Decisión |
| 45 | + |
| 46 | +**Opción A (ACTUAL):** Continuar usando herencia de las clases base de `Ums.Shell.Ddd`. |
| 47 | + |
| 48 | +**Opción B:** Refactorizar a diseño basado en composición, removiendo herencia del shell. |
| 49 | + |
| 50 | +**Opción C:** Crear un proyecto "Domain.Abstractions" con interfaces puras, removiendo MediatR del shell. |
| 51 | + |
| 52 | +### Decisión Tomada: **Opción A (Mantener Actual)** con justificación documentada. |
| 53 | + |
| 54 | +--- |
| 55 | + |
| 56 | +## Análisis de Pros y Contras |
| 57 | + |
| 58 | +### Opción A: Continuar Herencia de Shell.Ddd |
| 59 | + |
| 60 | +#### Pros ✅ |
| 61 | +| # | Pro | Justificación | |
| 62 | +|---|-----|---------------| |
| 63 | +| 1 | **Velocidad de Desarrollo** | Los equipos pueden enfocarse en lógica de negocio, no en infraestructura | |
| 64 | +| 2 | **Patrones Consistentes** | Todos los aggregates comparten comportamiento base idéntico | |
| 65 | +| 3 | **MediatR como Infraestructura** | MediatR está a nivel shell, no directamente en Domain | |
| 66 | +| 4 | **Battle-Tested** | Las shell libraries son compartidas entre múltiples proyectos | |
| 67 | +| 5 | **Menos Código a Mantener** | No se necesitan implementaciones duplicadas de AggregateRoot | |
| 68 | + |
| 69 | +#### Contras ❌ |
| 70 | +| # | Contra | Justificación | |
| 71 | +|---|--------|---------------| |
| 72 | +| 1 | **Violación de Pureza del Domain** | Domain referencia NuGet externo transitivamente | |
| 73 | +| 2 | **Riesgo de Filtración de Framework** | Si el shell evoluciona, el Domain podría verse forzado a cambiar | |
| 74 | +| 3 | **Complejidad en Testing** | Los tests del Domain requieren resolución de ensamblados de MediatR | |
| 75 | +| 4 | **Contaminación Conceptual** | El concepto de "Aggregate" del Domain conoce a MediatR | |
| 76 | +| 5 | **Pérdida de Portabilidad** | El Domain no puede extraerse como paquete separado | |
| 77 | + |
| 78 | +--- |
| 79 | + |
| 80 | +### Opción B: Refactorización Basada en Composición |
| 81 | + |
| 82 | +#### Pros ✅ |
| 83 | +| # | Pro | Justificación | |
| 84 | +|---|-----|---------------| |
| 85 | +| 1 | **Pureza Real del Domain** | Domain tiene cero dependencias externas | |
| 86 | +| 2 | **Extraíble** | El Domain podría publicarse como NuGet independiente | |
| 87 | +| 3 | **Testabilidad** | Los POCOs puros son más fáciles de testear unitariamente | |
| 88 | +| 4 | **Sin Acoplamiento Conceptual** | El Domain no conoce a MediatR | |
| 89 | + |
| 90 | +#### Contras ❌ |
| 91 | +| # | Contra | Justificación | |
| 92 | +|---|--------|---------------| |
| 93 | +| 1 | **Refactorización Masiva** | 100+ entities necesitan modificación | |
| 94 | +| 2 | **Pérdida de Consistencia** | Cada aggregate podría implementar patrones diferente | |
| 95 | +| 3 | **Proliferación de Boilerplate** | Comportamiento común duplicado entre aggregates | |
| 96 | +| 4 | **Cambio Rupturista** | Todos los bounded contexts afectados | |
| 97 | +| 5 | **Inversión de Tiempo** | Estimado 3-4 semanas de trabajo | |
| 98 | + |
| 99 | +--- |
| 100 | + |
| 101 | +### Opción C: Domain.Abstractions (Híbrido) |
| 102 | + |
| 103 | +Crear `Ums.Domain.Abstractions` con interfaces puras: |
| 104 | +- `IAggregateRoot<T>` |
| 105 | +- `IEntity<T, TProps>` |
| 106 | +- `IDomainEvents` |
| 107 | + |
| 108 | +Shell.Ddd implementa estas interfaces. Domain referencia solo Abstractions. |
| 109 | + |
| 110 | +#### Pros ✅ |
| 111 | +| # | Pro | Justificación | |
| 112 | +|---|-----|---------------| |
| 113 | +| 1 | **Claridad Arquitectónica** | Separación clara entre contratos e implementación | |
| 114 | +| 2 | **Pureza del Domain** | Domain solo depende de Abstractions (sin NuGets) | |
| 115 | +| 3 | **Flexibilidad** | Se puede cambiar implementación del shell si es necesario | |
| 116 | +| 4 | **Mantenible** | Cambios en MediatR no impactan al Domain | |
| 117 | + |
| 118 | +#### Contras ❌ |
| 119 | +| # | Contra | Justificación | |
| 120 | +|---|--------|---------------| |
| 121 | +| 1 | **Otra Capa** | Añade indirección sin beneficio inmediato | |
| 122 | +| 2 | **Riesgo de Over-Engineering** | 3 proyectos donde 2 podrían bastar | |
| 123 | +| 3 | **Esfuerzo de Migración** | Requiere crear proyecto Abstractions y actualizar referencias | |
| 124 | +| 4 | **Complejidad de Build** | Más proyectos = tiempos de build más largos | |
| 125 | + |
| 126 | +--- |
| 127 | + |
| 128 | +## Enfoque Seleccionado: Opción A (Actual) |
| 129 | + |
| 130 | +### Justificación |
| 131 | + |
| 132 | +1. **Regla BMAD R-10** establece: "Domain debe ser POCOs puros con cero referencias NuGet" - pero esta regla es **aspiracional** en interpretación estricta. La naturaleza transitiva de las dependencias del shell es un **compromiso controlado**. |
| 133 | + |
| 134 | +2. **Arquitectura Pragmática**: La librería shell es un **shared kernel** (concepto DDD). MediatR no es una preocupación del Domain - es infraestructura para manejar comandos/queries. El Domain no invoca MediatR directamente; solo usa clases base que happen to include it. |
| 135 | + |
| 136 | +3. **Evaluación de Riesgos**: |
| 137 | + - Si MediatR cambia versión mayor → Shell.Ddd se actualiza, Domain no cambia |
| 138 | + - Si Shell.Ddd cambia → Controlamos ambos, podemos migrar juntos |
| 139 | + - El riesgo de acoplamiento de MediatR en Domain está **contenido dentro del shell** |
| 140 | + |
| 141 | +4. **Contexto Histórico**: Esta arquitectura fue diseñada deliberadamente en ADR-0054 como trade-off entre pureza y productividad. |
| 142 | + |
| 143 | +--- |
| 144 | + |
| 145 | +## Notas de Implementación |
| 146 | + |
| 147 | +### Si Futuramente Elegimos Opción C (Domain.Abstractions) |
| 148 | + |
| 149 | +Path de migración: |
| 150 | +1. Crear proyecto `Ums.Domain.Abstractions` |
| 151 | +2. Definir interfaces `IAggregateRoot<T>`, `IEntity<T>`, `IDomainEvents` |
| 152 | +3. Hacer que `AggregateRoot<T>` en shell implemente estas interfaces |
| 153 | +4. Actualizar referencias del proyecto Domain de `Shell.Ddd` a `Shell.Ddd.Abstractions` |
| 154 | + |
| 155 | +### Validación |
| 156 | + |
| 157 | +Para verificar la severidad de la violación de pureza del Domain, ejecutar: |
| 158 | +```bash |
| 159 | +dotnet list src/apps/ums.api/Ums.Domain/Ums.Domain.csproj package |
| 160 | +# Debe mostrar CERO referencias directas a packages |
| 161 | +``` |
| 162 | + |
| 163 | +--- |
| 164 | + |
| 165 | +## Consecuencias |
| 166 | + |
| 167 | +### Positivas |
| 168 | +- Velocidad de desarrollo mantenida |
| 169 | +- Implementación consistente de aggregates en todos los bounded contexts |
| 170 | +- Las shell libraries pueden evolucionar independientemente |
| 171 | + |
| 172 | +### Negativas |
| 173 | +- La interpretación estricta de BMAD R-10 es violada |
| 174 | +- El Domain no puede publicarse como paquete standalone |
| 175 | +- Los equipos deben entender el modelo de dependencia transitiva |
| 176 | + |
| 177 | +### Mitigaciones |
| 178 | +- Documentar claramente esta decisión arquitectónica |
| 179 | +- Asegurar que shell.Ddd tenga garantías de estabilidad (política de versionado) |
| 180 | +- Considerar Opción C si la portabilidad se convierte en requerimiento |
| 181 | + |
| 182 | +--- |
| 183 | + |
| 184 | +## Referencias |
| 185 | + |
| 186 | +- [ADR-0054: Aislamiento de Shell Libraries](0054-shell-library-isolation.md) |
| 187 | +- [Regla BMAD R-10: Pureza del Domain](../.bmad-core/rules/global-rules.md) |
| 188 | +- [Documentación de MediatR](https://github.com/mattroberts297/MediatR) |
| 189 | +- [Patrón Shared Kernel de DDD](https://martinfowler.com/articles/refactoring-the-keynote/index.html#SharedKernel) |
0 commit comments