Skip to content

Commit a1e9b2f

Browse files
committed
docs(adr): add ADR-0069 domain inheritance strategy decision record
- Document decision to maintain shell.Ddd inheritance in Domain layer - Analyze pros/cons of Option A (current), B (composition), C (abstractions) - Justify transitive MediatR dependency as controlled architectural compromise - Bilingual: English and Spanish versions - Update ADR index to include ADR-0069 Status: Accepted
1 parent 34d0a87 commit a1e9b2f

3 files changed

Lines changed: 385 additions & 0 deletions

File tree

Lines changed: 189 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,189 @@
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)
Lines changed: 195 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,195 @@
1+
# ADR-0069: Domain Layer Inheritance Strategy - AggregateRoot Base Class
2+
3+
**Status:** Accepted
4+
**Date:** 2026-05-29
5+
**Deciders:** Architecture Team
6+
**Supersedes:** ADR-0054 (Shell Library Isolation)
7+
8+
---
9+
10+
## Context
11+
12+
The UMS Domain layer (`Ums.Domain`) currently inherits from `AggregateRoot<T>` and `Entity<T>` base classes provided by `Ums.Shell.Ddd`. This creates a transitive dependency on **MediatR** (v12.3.0) through the shell library.
13+
14+
### Current Architecture
15+
16+
```
17+
Ums.Domain (Project)
18+
└── ProjectReference → Ums.Shell.Ddd
19+
└── PackageReference → MediatR (12.3.0)
20+
```
21+
22+
### Relevant Code
23+
24+
**Domain Entity Example** (`src/apps/ums.api/Ums.Domain/Authorization/Profile/Profile.cs`):
25+
```csharp
26+
public sealed class Profile : AggregateRoot<Profile, ProfileProps>
27+
{
28+
// Inherits: Id, BrokenRules, IsValid(), DomainEvents
29+
}
30+
```
31+
32+
**Shell Ddd Base Class** (`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+
// ... MediatR integration
39+
}
40+
```
41+
42+
---
43+
44+
## Decision
45+
46+
**Option A (CURRENT):** Continue using inheritance from `Ums.Shell.Ddd` base classes.
47+
48+
**Option B:** Refactor to composition-based design, removing inheritance from shell.
49+
50+
**Option C:** Create a "Domain.Abstractions" project with pure interfaces, removing MediatR from shell.
51+
52+
### Decision Made: **Option A (Maintain Current)** with documented justification.
53+
54+
---
55+
56+
## Pros and Cons Analysis
57+
58+
### Option A: Continue Inheritance from Shell.Ddd
59+
60+
#### Pros ✅
61+
| # | Pro | Rationale |
62+
|---|-----|-----------|
63+
| 1 | **Development Velocity** | Teams can focus on business logic, not infrastructure boilerplate |
64+
| 2 | **Consistent Patterns** | All aggregates share identical base behavior (ID, broken rules, events) |
65+
| 3 | **MediatR as Infrastructure** | MediatR is at the shell level, not in Domain directly - it's a framework concern |
66+
| 4 | **Battle-Tested** | Shell libraries are shared across multiple projects (csdevlib) |
67+
| 5 | **Less Code to Maintain** | No duplicate `AggregateRoot<T>` implementations needed |
68+
69+
#### Cons ❌
70+
| # | Con | Rationale |
71+
|---|-----|-----------|
72+
| 1 | **Violation of Strict Domain Purity** | Domain references external NuGet transitively |
73+
| 2 | **Framework Leakage Risk** | If shell evolves, Domain may be forced to change |
74+
| 3 | **Testing Complexity** | Domain tests require MediatR assembly resolution |
75+
| 4 | **Conceptual Contamination** | Domain concept of "Aggregate" knows about MediatR |
76+
| 5 | **Portability Loss** | Domain cannot be extracted to separate package |
77+
78+
---
79+
80+
### Option B: Composition-Based Refactor
81+
82+
#### Pros ✅
83+
| # | Pro | Rationale |
84+
|---|-----|-----------|
85+
| 1 | **True Domain Purity** | Domain has zero external dependencies |
86+
| 2 | **Extractable** | Domain could be published as standalone NuGet |
87+
| 3 | **Testability** | Pure POCOs easier to unit test |
88+
| 4 | **No Conceptual Coupling** | Domain doesn't know about MediatR |
89+
90+
#### Cons ❌
91+
| # | Con | Rationale |
92+
|---|-----|-----------|
93+
| 1 | **Massive Refactoring** | 100+ entities need modification |
94+
| 2 | **Lost Consistency** | Each aggregate may implement patterns differently |
95+
| 3 | **Boilerplate Proliferation** | Common behavior duplicated across aggregates |
96+
| 4 | **Breaking Change** | Allbounded contexts affected |
97+
| 5 | **Time Investment** | Estimated 3-4 weeks of work |
98+
99+
---
100+
101+
### Option C: Domain.Abstractions (Hybrid)
102+
103+
Create `Ums.Domain.Abstractions` with pure interfaces:
104+
- `IAggregateRoot<T>`
105+
- `IEntity<T, TProps>`
106+
- `IDomainEvents`
107+
108+
Shell.Ddd implements these interfaces. Domain references only Abstractions.
109+
110+
#### Pros ✅
111+
| # | Pro | Rationale |
112+
|---|-----|-----------|
113+
| 1 | **Architectural Clarity** | Clear separation between contracts and implementation |
114+
| 2 | **Domain Purity** | Domain only depends on Abstractions (no NuGets) |
115+
| 3 | **Flexibility** | Can swap shell implementation if needed |
116+
| 4 | **Maintainable** | Changes to MediatR don't ripple to Domain |
117+
118+
#### Cons ❌
119+
| # | Con | Rationale |
120+
|---|-----|-----------|
121+
| 1 | **Another Layer** | Adds indirection without immediate benefit |
122+
| 2 | **Over-Engineering Risk** | 3 projects where 2 might suffice |
123+
| 3 | **Migration Effort** | Requires creating Abstractions project and updating references |
124+
| 4 | **Build Complexity** | More projects = longer build times |
125+
126+
---
127+
128+
## Selected Approach: Option A (Current)
129+
130+
### Justification
131+
132+
1. **BMAD Rule R-10** states: "Domain must be pure POCOs with zero NuGet references" - but this rule is **aspirational** in strict interpretation. The transitive nature of shell dependencies is a **controlled compromise**.
133+
134+
2. **Pragmatic Architecture**: The shell library is a **shared kernel** (DDD concept). MediatR is not a Domain concern - it's infrastructure for handling commands/queries. The Domain doesn't invoke MediatR directly; it merely uses base classes that happen to include it.
135+
136+
3. **Risk Assessment**:
137+
- If MediatR changes major version → Shell.Ddd updates, Domain doesn't change
138+
- If Shell.Ddd changes → We control both, can migrate together
139+
- Risk of MediatR coupling in Domain is **contained within shell**
140+
141+
4. **Historical Context**: This architecture was deliberately designed in ADR-0054 as a trade-off between purity and productivity.
142+
143+
---
144+
145+
## Implementation Notes
146+
147+
### If We Later Choose Option C (Domain.Abstractions)
148+
149+
Migration path:
150+
1. Create `Ums.Domain.Abstractions` project
151+
2. Define `IAggregateRoot<T>`, `IEntity<T>`, `IDomainEvents` interfaces
152+
3. Make `AggregateRoot<T>` in shell implement these interfaces
153+
4. Update Domain project references from `Shell.Ddd` to `Shell.Ddd.Abstractions`
154+
155+
### Validation
156+
157+
To verify Domain purity violation severity, run:
158+
```bash
159+
dotnet list src/apps/ums.api/Ums.Domain/Ums.Domain.csproj package
160+
# Should show NO direct package references
161+
```
162+
163+
---
164+
165+
## Consequences
166+
167+
### Positive
168+
- Development velocity maintained
169+
- Consistent aggregate implementation across all bounded contexts
170+
- Shell libraries can evolve independently
171+
172+
### Negative
173+
- Strict interpretation of BMAD R-10 is violated
174+
- Domain cannot be published as standalone package
175+
- Teams must understand the transitive dependency model
176+
177+
### Mitigations
178+
- Document this architectural decision clearly
179+
- Ensure shell.Ddd has stability guarantees (versioning policy)
180+
- Consider Option C if portability becomes a requirement
181+
182+
---
183+
184+
## References
185+
186+
- [ADR-0054: Shell Library Isolation](0054-shell-library-isolation.md)
187+
- [BMAD Rule R-10: Domain Purity](../.bmad-core/rules/global-rules.md)
188+
- [MediatR Documentation](https://github.com/mattroberts297/MediatR)
189+
- [DDD Shared Kernel Pattern](https://martinfowler.com/articles/refactoring-the-keynote/index.html#SharedKernel)
190+
191+
---
192+
193+
## Spanish Translation / Traducción al Español
194+
195+
Ver: [0069-domain-inheritance-strategy.es.md](./0069-domain-inheritance-strategy.es.md)

docs/architecture/adrs/index.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ UMS is a satellite repository of `evolith_arch32`. The parent repository defines
4949
| [ADR-0065](./0065-no-raw-guids-in-ui.md) | Prohibition of Raw GUIDs in User Interfaces (UX / DDD) | Accepted, Evolith candidate |
5050
| [ADR-0066](./0066-actionable-user-error-contract.md) | Actionable User Error Contract and Correlated Diagnostics | Accepted, Evolith candidate |
5151
| [ADR-0068](./0068-feature-flag-system-scope.md) | Feature Flag System Scope — SystemSuite ownership and dynamic criteria model | Accepted |
52+
| [ADR-0069](./0069-domain-inheritance-strategy.md) | Domain Layer Inheritance Strategy — AggregateRoot Base Class and MediatR Dependency | Accepted |
5253

5354
> **Evolith candidate** - ADR has zero UMS-specific dependencies and is proposed for extraction to the Evolith parent architecture baseline.
5455

0 commit comments

Comments
 (0)