Records no design orientado a domínios
Modele objetos de valor, DTOs e eventos de domínio como records para impor imutabilidade e melhorar a clareza do código.
Records no design orientado a domínios é uma aula grátis de C# Academy no CoddyKit. Esta é a aula 4 de 4. Você pode ler a aula completa abaixo gratuitamente — depois pratica ao vivo no navegador com um editor de código integrado e um tutor de IA 24/7. Faz parte do caminho de aprendizado de C# Academy, e seu progresso é sincronizado entre a web e o app CoddyKit. O curso de C# Academy inclui 4 aulas no total.
DDD e Objetos de Valor
No Desenvolvimento Orientado ao Domínio (DDD), um Objeto de Valor é definido por suas propriedades, não por sua identidade. Dois valores Money(100, "USD") são intercambiáveis. Os registros são a representação perfeita de objetos de valor em C#.
Objetos de Valor como Registros
Modele os objetos de valor do domínio como registros. A igualdade estrutural, a imutabilidade e a sintaxe concisa se alinham perfeitamente aos princípios 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); // TrueValidação nos Construtores de Objetos de Valor
Adicione um construtor compacto para validar invariantes. Os objetos de valor só devem poder ser criados em um estado válido — um estado inválido deve gerar uma exceção no momento da construção.
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 vs. Objetos de Valor
Entidades têm identidade (acompanhada pelo ID ao longo do tempo); Objetos de Valor não têm identidade e são intercambiáveis quando os valores coincidem. Modele-os de forma diferente em 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 Ricos com Métodos de Domínio
Os objetos de valor podem conter lógica de domínio na forma de métodos. As operações retornam novos objetos de valor em vez de alterar o 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 Domínio como Registros
Os eventos de domínio descrevem fatos que ocorreram no domínio. Os registros são ideais: são imutáveis, descritivos e usam igualdade estrutural, o que simplifica os testes.
// 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);DTOs e Contratos de API como Registros
Os DTOs de requisição e resposta são naturalmente objetos de valor — transportam dados sem identidade. Os registros tornam esses objetos concisos e imutáveis.
// 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);Armazenando Objetos de Valor no EF Core
Use entidades próprias para persistir objetos de valor no EF Core. As propriedades de cada objeto de valor são armazenadas na tabela do proprietário (ou em uma tabela separada), sem uma PK separada.
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);
});Raiz do Agregado com Objetos de Valor
Reúna tudo: uma raiz de agregado Order que usa registros para objetos de valor e eventos de domínio, com todas as regras de negócio aplicadas nos 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));
}
}Na Prática: Identificadores com Tipos Seguros
Use registros para encapsular identificadores primitivos e evitar a mistura acidental de identificadores de entidades diferentes — uma técnica comum de DDD chamada "identificadores fortemente tipados".
// 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!Verificação Rápida
O que define um Objeto de Valor em DDD e por que os registros são uma escolha natural?
Recapitulação: Registros no Desenvolvimento Orientado ao Domínio
Principais conclusões:
- Objetos de Valor: definidos pelos valores, sem identidade — use registros
- Entidades: têm identidade (ID) e estado mutável — use classes
- Valide invariantes em construtores compactos — um estado inválido não pode ser representado
- Eventos de domínio como registros: imutáveis, comparáveis e autoexplicativos
- Entidades próprias do EF Core para persistir objetos de valor
- Identificadores fortemente tipados usando registros evitam a mistura de identificadores de entidades
Perguntas Frequentes
A aula “Records no design orientado a domínios” é grátis?
Sim — o texto completo de “Records no design orientado a domínios” é grátis para ler aqui na web. Para praticá-la interativamente (um editor de código integrado e um tutor de IA 24/7) e desbloquear o restante do curso de C# Academy, atualize para CoddyKit PRO. O curso de C# Academy inclui 4 aulas no total.
O que vou aprender em “Records no design orientado a domínios”?
Modele objetos de valor, DTOs e eventos de domínio como records para impor imutabilidade e melhorar a clareza do código. Você pratica C# Academy com código prático que executa diretamente no navegador, e um tutor de IA 24/7 responde suas dúvidas enquanto trabalha na aula.
Preciso ter experiência prévia para começar C# Academy?
Nenhuma experiência prévia é necessária. C# Academy no CoddyKit é estruturado para alunos iniciantes até avançados, então você pode começar aqui ou desde o início e aprender no seu ritmo. Esta é a aula 4 de 4.
Quanto tempo leva a aula “Records no design orientado a domínios”?
A maioria das aulas CoddyKit leva cerca de 5–10 minutos. Cada uma é compacta e interativa, então você faz progresso constante e retoma exatamente de onde parou entre web e app.
Posso escrever e executar código nesta aula de C# Academy?
Sim. Cada aula de C# Academy inclui um editor de código integrado, então você escreve e executa código real direto no navegador e recebe feedback de IA instantaneamente — nenhuma configuração local necessária.
Todas as aulas deste curso
- Tipos record: fundamentos e sintaxe
- Imutabilidade com init e with
- Igualdade de valores e desconstrução
- Records no design orientado a domínios