Domain-Driven Design en C#: cómo no ahogarse en la complejidad

Entrenador de Python en Línea para Principiantes

Aprende Python fácilmente sin sobrecargarte de teoría. Resuelve tareas prácticas con verificación automática, recibe pistas y escribe código directamente en el navegador, sin instalar nada.

Start Course

Domain-Driven Design en C#: cómo no ahogarse en la complejidad

\n\n

Cuando su proyecto en C# supera la etapa de "aplicación CRUD simple", llega el momento de la verdad. La lógica de negocio comienza a enredarse, los métodos de los servicios crecen hasta cientos de líneas, y la palabra "refactorización" provoca un temblor nervioso. ¿Le suena familiar? Bienvenido al mundo donde, sin Domain-Driven Design (DDD), corre el riesgo de convertir el código en un pantano.

\n\n

DDD no es "otro framework más". Es una filosofía que coloca el dominio del negocio en el centro del universo. Hoy analizaremos cómo aplicar Domain-Driven Design en proyectos C# para que el código no solo funcione, sino que sea comprensible y flexible. Sin rodeos: solo ejemplos reales y consejos prácticos.

\n\n

¿Por qué DDD salva proyectos en C#?

\n\n

Imagine que está escribiendo un sistema para gestionar pedidos en una tienda en línea. Si simplemente coloca toda la lógica en los controladores de ASP.NET, en seis meses no podrá explicar por qué el método CreateOrder() llama a CalculateShipping(), que a su vez accede a la base de datos para obtener tipos de cambio. DDD propone una separación clara: existe una capa de dominio, donde vive la lógica de negocio, y una infraestructura, que se encarga de los detalles técnicos.

\n\n

La idea clave de DDD es el Ubiquitous Language (lenguaje ubicuo). Usted y los expertos del negocio hablan el mismo idioma. En el código, esto se expresa en términos como "Order", "LineItem", "DiscountPolicy", y no "OrderDto", "OrderService" o "Helper". Cuanto más se acerque el código al lenguaje del negocio, más fácil será mantenerlo.

\n\n

Diseño estratégico y táctico

\n\n

DDD se divide en dos partes: la estratégica (cómo dividir el sistema en módulos) y la táctica (cómo escribir código dentro del módulo). En C#, el diseño táctico nos proporciona cuatro bloques de construcción poderosos:

\n\n
    \n
  • Entity — objeto con un identificador único (por ejemplo, OrderId).
  • \n
  • Value Object — objeto que se define por sus atributos (por ejemplo, Money o Address).
  • \n
  • Aggregate Root — entidad raíz que gestiona un grupo de objetos.
  • \n
  • Repository — abstracción de acceso a datos.
  • \n
\n\n

Veamos cómo se ve esto en el código.

\n\n

Ejemplo: diseñando un pedido (Order)

\n\n

Empecemos con algo simple: tenemos un pedido que consta de líneas de artículo. El pedido puede estar en estado "Nuevo", "Pagado" o "Cancelado".

\n\n

1. Value Object: Money

\n\n

El dinero es un Value Object clásico. Dos objetos Money son iguales si tienen el mismo monto y moneda. Son inmutables.

\n\n
public sealed record Money(decimal Amount, string Currency)\n{\n    public static Money operator +(Money left, Money right)\n    {\n        if (left.Currency != right.Currency)\n            throw new InvalidOperationException("Currency mismatch");\n        return new Money(left.Amount + right.Amount, left.Currency);\n    }\n}\n
\n\n

Observe el uso de record — en C# 9+ es la forma ideal de crear un Value Object. Proporciona automáticamente Equals, GetHashCode e inmutabilidad.

\n\n

2. Entity: Order y LineItem

\n\n

Las entidades tienen un identificador. Creemos una clase base para Entity:

\n\n
public abstract class Entity<TId>\n{\n    public TId Id { get; protected set; }\n\n    protected Entity(TId id) => Id = id;\n\n    public override bool Equals(object? obj) =>\n        obj is Entity<TId> other && Id!.Equals(other.Id);\n\n    public override int GetHashCode() => Id!.GetHashCode();\n}\n
\n\n

Ahora, el propio Order y LineItem:

\n\n
public class Order : Entity<Guid>\n{\n    private readonly List<LineItem> _items = new();\n    public IReadOnlyList<LineItem> Items => _items.AsReadOnly();\n    public OrderStatus Status { get; private set; }\n    public Money Total { get; privat                

Blogs

Book Recommendations