DDD.Tooling.Abstractions
1.2.0
dotnet add package DDD.Tooling.Abstractions --version 1.2.0
NuGet\Install-Package DDD.Tooling.Abstractions -Version 1.2.0
<PackageReference Include="DDD.Tooling.Abstractions" Version="1.2.0" />
<PackageVersion Include="DDD.Tooling.Abstractions" Version="1.2.0" />
<PackageReference Include="DDD.Tooling.Abstractions" />
paket add DDD.Tooling.Abstractions --version 1.2.0
#r "nuget: DDD.Tooling.Abstractions, 1.2.0"
#:package DDD.Tooling.Abstractions@1.2.0
#addin nuget:?package=DDD.Tooling.Abstractions&version=1.2.0
#tool nuget:?package=DDD.Tooling.Abstractions&version=1.2.0
DDD.Tooling
Herramientas de análisis estático para validar reglas de Domain-Driven Design (DDD) en proyectos .NET mediante Roslyn Analyzers.
🎯 Objetivo
Proporcionar validación en tiempo de compilación de las reglas y patrones de DDD, ayudando a los desarrolladores a crear modelos de dominio consistentes y correctos.
✨ Características
- ✅ 16 Reglas de análisis DDD (DDD001-DDD016)
- ✅ 9 Code Fix Providers para corregir automáticamente errores/warnings
- ✅ Sugerencias educativas (Info) para mejorar el diseño
- ✅ Manejo inteligente de tipos (detecta automáticamente structs, nullables, referencias)
- ✅ Detección de referencias cruzadas entre Bounded Contexts (DDD010-DDD012)
- ✅ Soporte para Domain Events con validaciones de inmutabilidad y ciclo de vida (DDD014-DDD016)
- ✅ Soporte para colecciones genéricas (
List<T>,IReadOnlyCollection<T>, etc.) - ✅ Validación en tiempo real en el IDE
- ✅ Errores, Warnings e Infos claros con mensajes descriptivos
- ✅ Fácil integración vía NuGet (
DDD.Tooling.Analyzers)
📦 Estructura del Proyecto
DDD.Tooling/
├── DDD.Abstractions/ # Atributos DDD base
│ └── Attributes/
│ ├── EntityAttribute.cs
│ ├── EntityIdAttribute.cs
│ ├── AggregateRootAttribute.cs
│ ├── ValueObjectAttribute.cs
│ ├── BoundedContextAttribute.cs
│ ├── SharedKernelAttribute.cs
│ └── DomainEventAttribute.cs
├── DDD.Analyzers/ # Analizadores Roslyn
│ ├── DiagnosticDescriptors.cs
│ ├── EntityMustHaveEntityIdAnalyzer.cs # DDD001
│ ├── AggregateRootMustHaveEntityIdAnalyzer.cs # DDD002
│ ├── ValueObjectImmutabilityAnalyzer.cs # DDD004, DDD007, DDD008
│ ├── DddAttributeUsageAnalyzer.cs # DDD003, DDD005, DDD006
│ ├── EntityFactoryMethodAnalyzer.cs # DDD009
│ ├── BoundedContextDeclarationAnalyzer.cs # DDD010
│ ├── CrossBoundedContextReferenceAnalyzer.cs # DDD011, DDD012
│ ├── MultipleEntityIdAnalyzer.cs # DDD013
│ ├── DomainEventAnalyzer.cs # DDD014, DDD015, DDD016
│ └── CodeFixes/
│ ├── EntityIdCodeFixProvider.cs # Fix para DDD001/002
│ ├── EntityIdOnPropertyCodeFixProvider.cs # Fix para DDD003
│ ├── ValueObjectMutabilityCodeFixProvider.cs # Fix para DDD004/005/006
│ ├── EntityFactoryMethodCodeFixProvider.cs # Fix para DDD009
│ ├── BoundedContextDeclarationCodeFixProvider.cs # Fix para DDD010/016
│ ├── CrossBoundedContextReferenceCodeFixProvider.cs # Fix para DDD011
│ ├── DomainEventImmutabilityCodeFixProvider.cs # Fix para DDD014
│ └── DomainEventOccurredOnCodeFixProvider.cs # Fix para DDD015
├── DDD.Analyzers.Tests/ # Tests unitarios (91 tests)
│ ├── Analyzers/ # Tests DDD001–DDD016
│ ├── CodeFixes/ # Tests de Code Fixes
│ └── Helpers/
│ └── AnalyzerTestHelper.cs
└── TestDomain/ # Proyecto de prueba
├── Catalog/
│ ├── Course.cs
│ └── CourseModule.cs
├── StudentManagment/
│ └── Student.cs
└── SharedKernel/
└── Address.cs
🔍 Reglas Implementadas
DDD001 - Entity debe tener EntityId ❌ Error
Descripción: Todas las clases decoradas con [Entity] deben tener al menos una propiedad decorada con [EntityId].
Quick Fix disponible: Agrega [EntityId] public Guid Id { get; private set; } automáticamente.
Ejemplo incorrecto:
[Entity]
public class Product
{
public string Name { get; set; } // ❌ Falta [EntityId]
}
Ejemplo correcto:
[Entity]
public class Product
{
[EntityId]
public Guid Id { get; private set; } // ✅ Correcto
public string Name { get; set; }
}
DDD002 - AggregateRoot debe tener EntityId ❌ Error
Descripción: Todas las clases decoradas con [AggregateRoot] deben tener al menos una propiedad decorada con [EntityId].
Quick Fix disponible: Agrega [EntityId] public Guid Id { get; private set; } automáticamente.
DDD003 - EntityId solo en propiedades ❌ Error
Descripción: El atributo [EntityId] solo puede aplicarse a propiedades, no a campos u otros miembros.
Quick Fix disponible: Convierte el campo en una propiedad con { get; private set; } automáticamente.
DDD004 - ValueObject debe ser inmutable ⚠️ Warning
Descripción: Los Value Objects no deben tener setters públicos en sus propiedades para garantizar inmutabilidad.
Quick Fix disponible: Convierte el setter público a { get; private set; } o { get; } automáticamente.
Ejemplo incorrecto:
[ValueObject]
public class Money
{
public decimal Amount { get; set; } // ❌ Setter público
public string Currency { get; set; } // ❌ Setter público
}
Ejemplo correcto:
[ValueObject]
public class Money
{
public decimal Amount { get; } // ✅ Solo getter
public string Currency { get; } // ✅ Solo getter
public Money(decimal amount, string currency)
{
Amount = amount;
Currency = currency;
}
}
DDD005 - No puede ser Entity y ValueObject simultáneamente ❌ Error
Descripción: Una clase no puede estar decorada con [Entity] y [ValueObject] al mismo tiempo.
Quick Fix disponible: Elimina el atributo [ValueObject] automáticamente.
DDD006 - No puede ser AggregateRoot y ValueObject simultáneamente ❌ Error
Descripción: Una clase no puede estar decorada con [AggregateRoot] y [ValueObject] al mismo tiempo.
Quick Fix disponible: Elimina el atributo [ValueObject] automáticamente.
DDD007 - ValueObject debe sobrescribir Equals ⚠️ Warning
Descripción: Los ValueObjects deben compararse por valor, por lo que deben sobrescribir Equals(object).
Sin Quick Fix — los IDEs modernos (Visual Studio, Rider) ofrecen generar
EqualsyGetHashCodeautomáticamente con mejor resultado.
DDD008 - ValueObject debe sobrescribir GetHashCode ⚠️ Warning
Descripción: Los ValueObjects deben sobrescribir GetHashCode de forma consistente con Equals.
Sin Quick Fix — los IDEs modernos (Visual Studio, Rider) ofrecen generar
EqualsyGetHashCodeautomáticamente con mejor resultado.
DDD009 - Entity debería usar Factory Method ℹ️ Info
Descripción: Las Entities y AggregateRoots deben usar el patrón Factory Method: constructor privado/internal + método estático público que devuelva la instancia. Se reporta cuando el constructor es público o cuando no existe un factory method estático.
Quick Fix disponible (3 escenarios):
- Constructor público sin factory method → hace privado el constructor + agrega
Createestático - Constructor privado sin factory method estático → agrega
Createestático - Método
Createexistente pero no estático → agrega el modificadorstatic
Ejemplo que genera info:
[Entity]
public class Product // ℹ️ DDD009
{
[EntityId]
public Guid Id { get; private set; }
public Product(string name) // ⬅️ Constructor público
{
Id = Guid.NewGuid();
Name = name;
}
}
Ejemplo recomendado:
[Entity]
public class Product
{
[EntityId]
public Guid Id { get; private set; }
public static Product Create(string name) // ✅ Factory Method estático
{
return new Product(name);
}
private Product(string name) // ✅ Constructor privado
{
Id = Guid.NewGuid();
Name = name;
}
}
Ver documentación completa: DDD009_FACTORY_METHOD.md
DDD010 - Entity/AggregateRoot/ValueObject debe declarar su Bounded Context ⚠️ Warning
Descripción: Todas las clases DDD deben estar decoradas con [BoundedContext("NombreBC")] o [SharedKernel] para indicar a qué Bounded Context pertenecen. Esto es el prerequisito para que funcione DDD011.
Quick Fix disponible: Agrega [BoundedContext("NombreBC")] encima de la clase.
[AggregateRoot]
// ⚠️ DDD010: Falta declarar el Bounded Context
public class Course { ... }
// ✅ Correcto:
[AggregateRoot]
[BoundedContext("Catalog")]
public class Course { ... }
DDD011 - No referencias directas entre Bounded Contexts ❌ Error
Descripción: Una clase de un BC no puede tener propiedades públicas que referencien directamente tipos de otro BC. Se deben usar los identificadores (IDs) en su lugar.
Quick Fix disponible (2 casos):
- Tipo simple:
public Course Course { get; set; }→public Guid CourseId { get; set; } - Colección genérica:
public List<Course> Courses { get; set; }→public List<Guid> CourseIds { get; set; }
El mensaje varía según el tipo referenciado:
- AggregateRoot → "usa el
CourseId(el identificador del agregado)" - Entity interna → "las entidades internas no deben exponerse fuera del BC"
- ValueObject → "los value objects deben copiarse o abstraerse en el BC destino"
[AggregateRoot]
[BoundedContext("StudentManagement")]
public class Student
{
[EntityId]
public Guid Id { get; private set; }
public Course Course { get; set; } // ❌ DDD011 Error
public List<Course> Courses { get; set; } // ❌ DDD011 Error (genérico)
public Guid CourseId { get; set; } // ✅ Correcto: solo el Id
public List<Guid> CourseIds { get; set; } // ✅ Correcto: colección de Ids
}
DDD012 - Miembro privado usa tipo de otro Bounded Context ⚠️ Warning
Descripción: Campos y propiedades privadas/protegidas que referencian tipos de otro BC. Menos severo que DDD011 (privados no forman parte del contrato público), pero aún es una dependencia a revisar.
⚠️ No tiene Code Fix asociado — requiere decisión de diseño por parte del desarrollador.
[AggregateRoot]
[BoundedContext("StudentManagement")]
public class Student
{
private Course _currentCourse; // ⚠️ DDD012 Warning
private List<Course> _history; // ⚠️ DDD012 Warning
}
DDD013 - Solo puede haber un EntityId por clase ❌ Error
Descripción: Una clase decorada con [Entity] o [AggregateRoot] no puede tener más de una propiedad decorada con [EntityId]. En DDD, cada entidad tiene un único identificador.
⚠️ No tiene Code Fix asociado — requiere decisión de diseño por parte del desarrollador (¿cuál de los dos IDs es el correcto?).
Ejemplo incorrecto:
[Entity]
public class Product
{
[EntityId]
public Guid Id { get; private set; } // ❌ DDD013
[EntityId]
public Guid LegacyId { get; private set; } // ❌ DDD013: duplicado
}
Ejemplo correcto:
[Entity]
public class Product
{
[EntityId]
public Guid Id { get; private set; } // ✅ Un único EntityId
public Guid LegacyId { get; private set; } // ✅ Sin atributo
}
DDD014 - DomainEvent debe ser inmutable ❌ Error
Descripción: Las clases decoradas con [DomainEvent] no deben tener setters públicos. Los Domain Events son registros históricos inmutables.
Quick Fix disponible: Convierte el setter público a private set o init (C# 9+).
Ejemplo incorrecto:
[DomainEvent]
[BoundedContext("Catalog")]
public class CoursePublishedEvent
{
public string Title { get; set; } // ❌ DDD014: setter público
public DateTime OccurredOn { get; }
}
Ejemplo correcto:
[DomainEvent]
[BoundedContext("Catalog")]
public class CoursePublishedEvent
{
public string Title { get; } // ✅ Solo getter
public DateTime OccurredOn { get; }
}
DDD015 - DomainEvent debe tener propiedad OccurredOn ⚠️ Warning
Descripción: Los Domain Events deben registrar cuándo ocurrieron. Se requiere una propiedad OccurredOn de tipo DateTime o DateTimeOffset.
Quick Fix disponible: Agrega public DateTime OccurredOn { get; } como primer miembro. Añade using System; si falta.
// ⚠️ DDD015: falta OccurredOn
[DomainEvent]
[BoundedContext("Catalog")]
public class CoursePublishedEvent { }
// ✅ Correcto:
[DomainEvent]
[BoundedContext("Catalog")]
public class CoursePublishedEvent
{
public DateTime OccurredOn { get; }
}
DDD016 - DomainEvent debe declarar su Bounded Context ⚠️ Warning
Descripción: Los Domain Events, al igual que el resto de tipos DDD, deben declarar a qué Bounded Context pertenecen con [BoundedContext("Nombre")] o [SharedKernel].
Quick Fix disponible: BoundedContextDeclarationCodeFixProvider (extendido para soportar [DomainEvent]).
🚀 Uso
1. Instalar las abstracciones
Agrega referencia al proyecto DDD.Abstractions:
<ItemGroup>
<ProjectReference Include="..\DDD.Abstractions\DDD.Abstractions.csproj" />
</ItemGroup>
2. Instalar los analizadores
Agrega referencia al proyecto DDD.Analyzers como analizador:
<ItemGroup>
<ProjectReference Include="..\DDD.Analyzers\DDD.Analyzers.csproj"
OutputItemType="Analyzer"
ReferenceOutputAssembly="false" />
</ItemGroup>
3. Usar los atributos en tu código
using DDD.Abstractions;
using System;
[Entity]
public class Customer
{
[EntityId]
public Guid CustomerId { get; private set; }
public string Name { get; set; }
public Customer(Guid id, string name)
{
CustomerId = id;
Name = name;
}
}
[ValueObject]
public class Email
{
public string Value { get; }
public Email(string value)
{
if (string.IsNullOrWhiteSpace(value))
throw new ArgumentException("Email no puede estar vacío");
Value = value;
}
}
🔧 Compilación
# Compilar toda la solución
dotnet build
# Compilar solo los analizadores
dotnet build DDD.Analyzers/DDD.Analyzers.csproj
# Empaquetar los analizadores
dotnet pack DDD.Analyzers/DDD.Analyzers.csproj
📝 Ejemplos en TestDomain
El proyecto TestDomain contiene ejemplos de uso correcto e incorrecto, organizados por Bounded Context:
- ✅
Catalog/Course.cs- AggregateRoot con[BoundedContext("Catalog")] - ✅
Catalog/CourseModule.cs- Entity interna del BC Catalog - ✅
StudentManagment/Student.cs- AggregateRoot con referencias cruzadas (activa DDD011) - ✅
SharedKernel/Address.cs- ValueObject con[SharedKernel]
📚 Documentación Adicional
- 🚀 QUICKSTART.md - Guía rápida de inicio
- 🗺️ ROADMAP.md - Roadmap del proyecto
- 🎬 DEMO.md - Demo completo con ejemplos
- 📋 CHANGELOG.md - Historial de cambios
🎯 Estado del Proyecto
- ✅ 16 reglas implementadas y testeadas (DDD001–DDD016)
- ✅ 9 Code Fix Providers implementados y testeados
- ✅ 91 tests unitarios — 100% verdes (68 analyzer + 23 codefix)
- ✅ Publicado en NuGet.org —
DDD.Tooling.Abstractions·DDD.Tooling.Analyzers
📄 Licencia
Este proyecto está bajo la licencia MIT. Ver el archivo LICENSE para más detalles.
👤 Autor
Gastón Chatelet — Creador y mantenedor principal del proyecto.
Contribuciones
Las contribuciones son bienvenidas. Por favor, abre un issue antes de enviar un pull request.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net5.0 was computed. net5.0-windows was computed. net6.0 was computed. net6.0-android was computed. net6.0-ios was computed. net6.0-maccatalyst was computed. net6.0-macos was computed. net6.0-tvos was computed. net6.0-windows was computed. net7.0 was computed. net7.0-android was computed. net7.0-ios was computed. net7.0-maccatalyst was computed. net7.0-macos was computed. net7.0-tvos was computed. net7.0-windows was computed. net8.0 was computed. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 was computed. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 was computed. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
| .NET Core | netcoreapp2.0 was computed. netcoreapp2.1 was computed. netcoreapp2.2 was computed. netcoreapp3.0 was computed. netcoreapp3.1 was computed. |
| .NET Standard | netstandard2.0 is compatible. netstandard2.1 was computed. |
| .NET Framework | net461 was computed. net462 was computed. net463 was computed. net47 was computed. net471 was computed. net472 was computed. net48 was computed. net481 was computed. |
| MonoAndroid | monoandroid was computed. |
| MonoMac | monomac was computed. |
| MonoTouch | monotouch was computed. |
| Tizen | tizen40 was computed. tizen60 was computed. |
| Xamarin.iOS | xamarinios was computed. |
| Xamarin.Mac | xamarinmac was computed. |
| Xamarin.TVOS | xamarintvos was computed. |
| Xamarin.WatchOS | xamarinwatchos was computed. |
-
.NETStandard 2.0
- No dependencies.
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
v1.2.0 - Runtime support: IDomainEvent interface. IEntity<TId>, IAggregateRoot<TId> interfaces. Nuevas base classes Entity<TId>, AggregateRoot<TId>, ValueObject con implementación completa de igualdad y eventos de dominio. Companion: DDD.Tooling.Analyzers 1.2.0.