Records im Domain-Driven Design
Modellieren Sie Value Objects, DTOs und Domain Events als Records, um Unveränderlichkeit durchzusetzen und die Verständlichkeit des Codes zu verbessern.
Records im Domain-Driven Design ist eine kostenlose C# Academy-Lektion auf CoddyKit. Dies ist Lektion 4 von 4. Du kannst die komplette Lektion unten kostenlos lesen – dann übst du sie direkt im Browser mit einem integrierten Code-Editor und einem KI-Tutor rund um die Uhr. Sie ist Teil des C# Academy-Lernpfads, und dein Fortschritt wird über Web und CoddyKit-App synchronisiert. Der C# Academy-Kurs umfasst insgesamt 4 Lektionen.
DDD und Value Objects
Im Domain-Driven Design (DDD) wird ein Value Object durch seine Eigenschaften und nicht durch seine Identität definiert. Zwei Werte Money(100, "USD") sind austauschbar. Records sind die perfekte C#-Darstellung für Value Objects.
Value Objects als Records
Modellieren Sie Domänen-Value-Objects als Records. Ihre strukturelle Gleichheit, Unveränderlichkeit und kompakte Syntax passen perfekt zu den DDD-Prinzipien.
// 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); // TrueValidierung in Value-Object-Konstruktoren
Fügen Sie einen kompakten Konstruktor hinzu, um Invarianten zu validieren. Value Objects sollten nur in einem gültigen Zustand erstellt werden können — ein ungültiger Zustand sollte bereits bei der Erstellung eine Ausnahme auslösen.
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 ArgumentExceptionEntities vs. Value Objects
Entities haben eine Identität, die über die Zeit anhand einer ID verfolgt wird; Value Objects haben keine Identität und sind austauschbar, wenn ihre Werte übereinstimmen. Modellieren Sie sie in C# unterschiedlich.
// 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 valueValue Objects mit Domänenmethoden
Value Objects können Domänenlogik in Form von Methoden enthalten. Operationen geben neue Value Objects zurück, anstatt das vorhandene zu verändern.
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);
}Domänenereignisse als Records
Domänenereignisse beschreiben Dinge, die in der Domäne geschehen sind. Records eignen sich dafür perfekt: Sie sind unveränderlich, beschreibend und verwenden strukturelle Gleichheit, was das Testen vereinfacht.
// 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 und API-Verträge als Records
Request-/Response-DTOs sind ganz natürlich Value Objects — sie transportieren Daten ohne Identität. Records machen sie kompakt und unveränderlich.
// 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);Value Objects in EF Core speichern
Verwenden Sie Owned Entities, um Value Objects in EF Core zu speichern. Die Eigenschaften jedes Value Objects werden in der Tabelle des Besitzers (oder in einer separaten Tabelle) gespeichert, ohne einen eigenen Primärschlüssel.
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);
});Aggregate Root mit Value Objects
Führen wir alles zusammen: ein Order-Aggregate-Root, das Records für Value Objects und Domänenereignisse verwendet und alle Geschäftsregeln in Methoden durchsetzt.
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));
}
}Praxisbeispiel: Typsichere IDs
Verwenden Sie Records, um primitive IDs zu kapseln und das versehentliche Vermischen von IDs verschiedener Entities zu verhindern — eine verbreitete DDD-Technik namens "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!Kurzer Test
Wodurch wird ein DDD-Value-Object definiert, und warum eignen sich Records dafür besonders gut?
Zusammenfassung: Records im Domain-Driven Design
Wichtigste Erkenntnisse:
- Value Objects: durch Werte definiert, ohne Identität — verwenden Sie Records
- Entities: besitzen eine Identität (ID) und veränderlichen Zustand — verwenden Sie Klassen
- Validieren Sie Invarianten in kompakten Konstruktoren — ein ungültiger Zustand ist nicht darstellbar
- Domänenereignisse als Records: unveränderlich, vergleichbar und selbstdokumentierend
- Owned Entities in EF Core zum Speichern von Value Objects
- Stark typisierte IDs mit Records verhindern das Vermischen von Entity-IDs
Häufig gestellte Fragen
Ist die Lektion „Records im Domain-Driven Design“ kostenlos?
Ja — der vollständige Text von „Records im Domain-Driven Design“ ist hier im Web kostenlos zu lesen. Um sie interaktiv zu üben (integrierter Code-Editor und 24/7 KI-Tutor) und den Rest des C# Academy-Kurses freizuschalten, upgrade auf CoddyKit PRO. Der C# Academy-Kurs umfasst insgesamt 4 Lektionen.
Was lerne ich in „Records im Domain-Driven Design“?
Modellieren Sie Value Objects, DTOs und Domain Events als Records, um Unveränderlichkeit durchzusetzen und die Verständlichkeit des Codes zu verbessern. Du übst C# Academy mit praktischem Code, den du direkt im Browser ausführst, und ein 24/7 KI-Tutor beantwortet deine Fragen während du die Lektion bearbeitest.
Brauche ich Erfahrung, um C# Academy zu starten?
Keine Vorkenntnisse erforderlich. C# Academy auf CoddyKit ist für Anfänger bis fortgeschrittene Lernende strukturiert, sodass du hier starten oder von Anfang an beginnen und in deinem eigenen Tempo voranschreiten kannst. Dies ist Lektion 4 von 4.
Wie lange dauert die Lektion „Records im Domain-Driven Design“?
Die meisten CoddyKit-Lektionen dauern etwa 5–10 Minuten. Jede ist kompakt und interaktiv, sodass du stetig Fortschritte machst und genau dort weitermachst, wo du aufgehört hast – im Web und in der App.
Kann ich in dieser C# Academy-Lektion Code schreiben und ausführen?
Ja. Jede C# Academy-Lektion enthält einen integrierten Code-Editor, sodass du echten Code direkt in deinem Browser schreibst und ausführst und sofort KI-Feedback erhältst — ohne lokale Einrichtung erforderlich.
Alle Lektionen in diesem Kurs
- Record-Typen: Grundlagen und Syntax
- Unveränderlichkeit mit init und with
- Wertgleichheit und Dekonstruktion
- Records im Domain-Driven Design