Records en el diseño guiado por el dominio
Modele objetos de valor, DTO y eventos de dominio como records para imponer la inmutabilidad y mejorar la claridad del código.
Records en el diseño guiado por el dominio es una lección gratuita de C# Academy en CoddyKit. Esta es la lección 4 de 4. Puedes leer la lección completa abajo gratuitamente — luego la practicas en el navegador con un editor de código integrado y un tutor de IA 24/7. Forma parte de la ruta de aprendizaje de C# Academy, y tu progreso se sincroniza en la web y la app de CoddyKit. El curso de C# Academy incluye 4 lecciones en total.
DDD y objetos de valor
En el diseño impulsado por el dominio (DDD), un objeto de valor se define por sus propiedades, no por su identidad. Dos valores Money(100, "USD") son intercambiables. Los registros son la representación perfecta de los objetos de valor en C#.
Objetos de valor como registros
Modele los objetos de valor del dominio como registros. Su igualdad estructural, inmutabilidad y sintaxis concisa encajan perfectamente con los principios de DDD.
// Value objects — defined by their values, not identity
public record Money(decimal Amount, string Currency);
public record Email(string Value);
public record PhoneNumber(string CountryCode, string Number);
public record Address(string Street, string City, string Country, string PostalCode);
// Two Money objects with same values are equal:
var price = new Money(99.99m, "USD");
var same = new Money(99.99m, "USD");
Console.WriteLine(price == same); // TrueValidación en los constructores de objetos de valor
Añada un constructor compacto para validar las invariantes. Los objetos de valor solo deberían poder crearse en un estado válido: un estado no válido debe provocar una excepción durante la construcción.
public record Email(string Value)
{
// Compact constructor for validation
public Email : this(Value)
{
if (string.IsNullOrWhiteSpace(Value) || !Value.Contains('@'))
throw new ArgumentException("Invalid email address", nameof(Value));
Value = Value.Trim().ToLowerInvariant();
}
}
// Usage:
var email = new Email(" Alice@Example.COM "); // normalized to alice@example.com
new Email("not-an-email"); // throws ArgumentExceptionEntidades frente a objetos de valor
Las entidades tienen identidad (se siguen mediante un ID a lo largo del tiempo); los objetos de valor no tienen identidad y son intercambiables cuando sus valores coinciden. Modélelos de forma diferente en C#.
// ENTITY: has identity, mutable state
public class Customer
{
public Guid Id { get; } = Guid.NewGuid();
public string Name { get; set; } = "";
public Email Email { get; set; } = default!; // value object
}
// VALUE OBJECT: no identity, immutable
public record Email(string Value);
public record Money(decimal Amount, string Currency);
// Two customers with same name are DIFFERENT entities
// Two Money(100, "USD") are the SAME valueObjetos de valor enriquecidos con métodos de dominio
Los objetos de valor pueden contener lógica de dominio en forma de métodos. Las operaciones devuelven objetos de valor nuevos en lugar de modificar el existente.
public record Money(decimal Amount, string Currency)
{
public Money Add(Money other)
{
if (Currency != other.Currency)
throw new DomainException("Cannot add different currencies");
return this with { Amount = Amount + other.Amount };
}
public Money Subtract(Money other) =>
Amount >= other.Amount
? this with { Amount = Amount - other.Amount }
: throw new DomainException("Insufficient funds");
public Money ApplyDiscount(decimal percent) =>
this with { Amount = Amount * (1 - percent / 100) };
public static Money Zero(string currency) => new(0, currency);
}Eventos de dominio como registros
Los eventos de dominio describen hechos que ocurrieron en el dominio. Los registros son perfectos: son inmutables, descriptivos y usan igualdad estructural, lo que simplifica las pruebas.
// Domain events as records
public abstract record DomainEvent(DateTime OccurredAt);
public record OrderPlaced(
Guid OrderId,
Guid CustomerId,
Money Total,
DateTime OccurredAt) : DomainEvent(OccurredAt);
public record OrderShipped(
Guid OrderId,
string TrackingNumber,
DateTime OccurredAt) : DomainEvent(OccurredAt);
public record PaymentReceived(
Guid OrderId,
Money Amount,
DateTime OccurredAt) : DomainEvent(OccurredAt);DTO y contratos de API como registros
Los DTO de solicitud y respuesta son objetos de valor por naturaleza: transportan datos, pero no tienen identidad. Los registros los hacen concisos e inmutables.
// Request DTOs
public record CreateOrderCommand(
Guid CustomerId,
IReadOnlyList<OrderLineDto> Lines);
public record OrderLineDto(Guid ProductId, int Quantity);
// Response DTOs
public record OrderCreatedResponse(
Guid OrderId,
string Status,
decimal Total,
DateTime CreatedAt);
// These can be compared in tests by value:
var expected = new OrderCreatedResponse(id, "Pending", 99m, now);
Assert.Equal(expected, actual);Almacenamiento de objetos de valor en EF Core
Use entidades propietarias para persistir objetos de valor en EF Core. Las propiedades de cada objeto de valor se almacenan en la tabla del propietario (o en una tabla independiente) sin una PK propia.
public class Order
{
public int Id { get; set; }
public Money Total { get; set; } = new(0, "USD");
public Address ShipTo { get; set; } = default!;
}
// EF Core config:
modelBuilder.Entity<Order>(e =>
{
e.OwnsOne(o => o.Total, money =>
{
money.Property(m => m.Amount).HasColumnType("decimal(18,2)");
money.Property(m => m.Currency).HasMaxLength(3);
});
e.OwnsOne(o => o.ShipTo);
});Raíz del agregado con objetos de valor
Reúna todos los conceptos: una raíz de agregado Order que usa registros para los objetos de valor y los eventos de dominio, con todas las reglas de negocio aplicadas en los métodos.
public class Order
{
private readonly List<DomainEvent> _events = new();
public IReadOnlyList<DomainEvent> DomainEvents => _events;
public Guid Id { get; } = Guid.NewGuid();
public Address ShipTo { get; private set; } = default!; // value obj
public Money Total { get; private set; } = Money.Zero("USD");
public void PlaceOrder(Address shipTo, IEnumerable<OrderLine> lines)
{
ShipTo = shipTo;
Total = lines.Aggregate(Money.Zero("USD"),
(acc, l) => acc.Add(l.Price));
_events.Add(new OrderPlaced(Id, CustomerId, Total, DateTime.UtcNow));
}
}Caso real: identificadores con tipos seguros
Use registros para envolver los ID primitivos y evitar mezclar accidentalmente los ID de distintas entidades: una técnica común de DDD denominada "Strongly Typed IDs".
// Strongly typed IDs — can't confuse CustomerId with OrderId
public record CustomerId(Guid Value)
{
public static CustomerId New() => new(Guid.NewGuid());
public static implicit operator Guid(CustomerId id) => id.Value;
}
public record OrderId(Guid Value)
{
public static OrderId New() => new(Guid.NewGuid());
}
// Type safety:
void ShipOrder(OrderId orderId, CustomerId customerId) { }
ShipOrder(OrderId.New(), CustomerId.New()); // OK
ShipOrder(CustomerId.New(), OrderId.New()); // COMPILE ERROR!Comprobación rápida
¿Qué define un objeto de valor de DDD y por qué los registros encajan de forma natural?
Resumen: registros en el diseño impulsado por el dominio
Conclusiones clave:
- Objetos de valor: definidos por sus valores, sin identidad; use registros
- Entidades: tienen identidad (ID) y estado mutable; use clases
- Valide las invariantes en constructores compactos: un estado no válido no se puede representar
- Eventos de dominio como registros: inmutables, comparables y autoexplicativos
- Entidades propietarias de EF Core para persistir objetos de valor
- Los ID con tipos seguros mediante registros evitan mezclar los ID de las entidades
Preguntas frecuentes
¿La lección «Records en el diseño guiado por el dominio» es gratis?
Sí — el texto completo de «Records en el diseño guiado por el dominio» es gratis para leer aquí en la web. Para practicarla de forma interactiva (editor de código integrado y tutor de IA 24/7) y desbloquear el resto del curso de C# Academy, actualiza a CoddyKit PRO. El curso de C# Academy incluye 4 lecciones en total.
¿Qué aprenderé en «Records en el diseño guiado por el dominio»?
Modele objetos de valor, DTO y eventos de dominio como records para imponer la inmutabilidad y mejorar la claridad del código. Practicas C# Academy con código real que ejecutas directamente en el navegador, y un tutor de IA 24/7 responde tus preguntas mientras trabajas en la lección.
¿Necesito experiencia previa para empezar C# Academy?
No se requiere experiencia previa. C# Academy en CoddyKit está estructurado para principiantes hasta estudiantes avanzados, así que puedes empezar aquí o desde el inicio y avanzar a tu ritmo. Esta es la lección 4 de 4.
¿Cuánto tiempo toma la lección «Records en el diseño guiado por el dominio»?
La mayoría de las lecciones de CoddyKit toman alrededor de 5–10 minutos. Cada una es compacta e interactiva, así que avanzas constantemente y retomas exactamente por donde dejaste en la web y la app.
¿Puedo escribir y ejecutar código en esta lección de C# Academy?
Sí. Cada lección de C# Academy incluye un editor de código integrado, así que escribes y ejecutas código real directamente en tu navegador y obtienes retroalimentación instantánea de IA — sin configuración local necesaria.
Todas las lecciones de este curso
- Tipos record: fundamentos y sintaxis
- Inmutabilidad con init y with
- Igualdad de valores y deconstrucción
- Records en el diseño guiado por el dominio