DDD.Tooling.Analyzers 1.2.0

dotnet add package DDD.Tooling.Analyzers --version 1.2.0
                    
NuGet\Install-Package DDD.Tooling.Analyzers -Version 1.2.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="DDD.Tooling.Analyzers" Version="1.2.0">
  <PrivateAssets>all</PrivateAssets>
  <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
</PackageReference>
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="DDD.Tooling.Analyzers" Version="1.2.0" />
                    
Directory.Packages.props
<PackageReference Include="DDD.Tooling.Analyzers">
  <PrivateAssets>all</PrivateAssets>
  <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
</PackageReference>
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add DDD.Tooling.Analyzers --version 1.2.0
                    
#r "nuget: DDD.Tooling.Analyzers, 1.2.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package DDD.Tooling.Analyzers@1.2.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=DDD.Tooling.Analyzers&version=1.2.0
                    
Install as a Cake Addin
#tool nuget:?package=DDD.Tooling.Analyzers&version=1.2.0
                    
Install as a Cake Tool

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 Equals y GetHashCode automá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 Equals y GetHashCode automá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 Create estático
  • Constructor privado sin factory method estático → agrega Create estático
  • Método Create existente pero no estático → agrega el modificador static

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

🎯 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.

There are no supported framework assets in this package.

Learn more about Target Frameworks and .NET Standard.

This package has 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.

Version Downloads Last Updated
1.2.0 192 3/18/2026
1.1.0 116 3/17/2026
1.0.1 116 3/17/2026
1.0.0 139 3/13/2026

v1.2.0 - Runtime support: IDomainEvent, IEntity<TId>, IAggregateRoot<TId>, Entity<TId>, AggregateRoot<TId>, ValueObject base classes. 4 nuevos analizadores (DDD017-DDD020) + 4 Code Fixes. Herencia automática detectada en DDD001/002/007/008. Requiere DDD.Tooling.Abstractions 1.2.0.