Records i domändriven design
Modellera värdeobjekt, DTO:er och domänhändelser som records för att framtvinga oföränderlighet och göra koden tydligare.
Records i domändriven design är en gratis lektion i C# Academy på CoddyKit. Detta är lektion 4 av 4. Ni kan läsa hela lektionen gratis nedan och sedan öva praktiskt i webbläsaren med en inbyggd kodredigerare och en AI-handledare som är tillgänglig dygnet runt. Den ingår i lärvägen för C# Academy, och Era framsteg synkroniseras mellan webben och CoddyKit-appen. Kursen i C# Academy innehåller totalt 4 lektioner.
DDD och värdeobjekt
I Domain-Driven Design (DDD) definieras ett värdeobjekt av sina egenskaper, inte av sin identitet. Två Money(100, "USD")-värden är utbytbara. Records är den perfekta C#-representationen för värdeobjekt.
Värdeobjekt som records
Modellera domänens värdeobjekt som records. Deras strukturella likhet, oföränderlighet och koncisa syntax överensstämmer perfekt med DDD-principerna.
// 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); // TrueValidering i värdeobjektens konstruktorer
Lägg till en kompakt konstruktor för att validera invarianter. Värdeobjekt ska bara kunna skapas i ett giltigt tillstånd — ett ogiltigt tillstånd ska orsaka ett undantag vid skapandet.
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 ArgumentExceptionEntiteter kontra värdeobjekt
Entiteter har en identitet (spåras med ID över tid); värdeobjekt har ingen identitet och är utbytbara om värdena överensstämmer. Modellera dem på olika sätt i 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 valueRika värdeobjekt med domänmetoder
Värdeobjekt kan innehålla domänlogik i form av metoder. Operationer returnerar nya värdeobjekt i stället för att ändra det befintliga.
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änhändelser som records
Domänhändelser beskriver sådant som har inträffat i domänen. Records passar perfekt: de är oföränderliga och beskrivande och använder strukturell likhet, vilket förenklar testning.
// 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:er och API-kontrakt som records
DTO:er för förfrågningar och svar är naturligt värdeobjekt — de innehåller data utan identitet. Records gör dem kortfattade och oföränderliga.
// 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);Lagring av värdeobjekt i EF Core
Använd ägda entiteter för att spara värdeobjekt i EF Core. Egenskaperna i varje värdeobjekt lagras i ägarens tabell (eller i en separat tabell) utan en separat primärnyckel.
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);
});Aggregatrot med värdeobjekt
Sammanför allt: en Order-aggregatrot som använder records för värdeobjekt och domänhändelser, med alla affärsregler implementerade i metoder.
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));
}
}Verklighetsnära: typsäkra ID:n
Använd records för att omsluta primitiva ID:n och förhindra att olika entitets-ID:n blandas ihop av misstag — en vanlig DDD-teknik som kallas "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!Snabbtest
Vad definierar ett DDD-värdeobjekt, och varför passar records naturligt för detta?
Repetition: records i domändriven design
Viktiga slutsatser:
- Värdeobjekt: definieras av värden, saknar identitet — använd records
- Entiteter: har identitet (ID) och föränderligt tillstånd — använd klasser
- Validera invarianter i kompakta konstruktorer — ett ogiltigt tillstånd kan inte representeras
- Domänhändelser som records: oföränderliga, jämförbara och självbeskrivande
- Ägda entiteter i EF Core för att spara värdeobjekt
- Starkt typade ID:n med records förhindrar att entitets-ID:n blandas ihop
Lär dig C# med en AI-lärare – gratis
Skriv och kör riktig kod i webbläsaren, få omedelbar hjälp av en AI-lärare dygnet runt och fortsätt där du slutade – på webben eller i appen.
- Kurser
- 93
- Lektioner
- 346
Vanliga frågor
Är lektionen ”Records i domändriven design” gratis?
Ja – hela texten till ”Records i domändriven design” kan läsas gratis här på webben. Om Ni vill öva interaktivt med en inbyggd kodredigerare och en AI-handledare som är tillgänglig dygnet runt och låsa upp resten av kursen i C# Academy, kan Ni uppgradera till CoddyKit PRO. Kursen i C# Academy innehåller totalt 4 lektioner.
Vad lär jag mig i ”Records i domändriven design”?
Modellera värdeobjekt, DTO:er och domänhändelser som records för att framtvinga oföränderlighet och göra koden tydligare. Ni övar på C# Academy med praktisk kod som körs direkt i webbläsaren, medan en AI-handledare som är tillgänglig dygnet runt svarar på Era frågor under lektionen.
Behöver jag någon erfarenhet för att börja lära mig C# Academy?
Du behöver inga förkunskaper. Utbildningen i C# Academy på CoddyKit är upplagd för allt från nybörjare till avancerade elever, så att du kan börja här eller från början och gå fram i din egen takt. Detta är lektion 4 av 4.
Hur lång tid tar lektionen ”Records i domändriven design”?
De flesta CoddyKit-lektioner tar cirka 5–10 minuter. Varje lektion är kort och interaktiv, så att du gör stadiga framsteg och kan fortsätta precis där du slutade – på webben eller i appen.
Kan jag skriva och köra kod i den här C# Academy-lektionen?
Ja. Varje C# Academy-lektion innehåller en inbyggd kodredigerare, så att du kan skriva och köra riktig kod direkt i webbläsaren och få omedelbar AI-feedback – utan lokal installation.
Alla lektioner i den här kursen
- Record-typer: grunder och syntax
- Oföränderlighet med init och with
- Värdelikhet och dekomponering
- Records i domändriven design