C# Academy · Lezione

Record nel Domain-Driven Design

Modelli oggetti valore, DTO ed eventi di dominio come record per garantire l'immutabilità e migliorare la chiarezza del codice.

Lezione 4 di 412 passaggi

Record nel Domain-Driven Design è una lezione C# Academy gratuita su CoddyKit. Questa è la lezione 4 di 4. Puoi leggere la lezione completa qui gratuitamente — poi esercitati direttamente nel browser con un editor di codice integrato e un tutor IA disponibile 24/7. Fa parte del percorso di apprendimento C# Academy, e i tuoi progressi si sincronizzano tra il web e l'app CoddyKit. Il corso C# Academy include 4 lezioni in totale.

DDD e Value Object

Nel Domain-Driven Design (DDD), un Value Object è definito dalle sue proprietà, non dalla sua identità. Due valori Money(100, "USD") sono intercambiabili. I record sono la rappresentazione C# ideale per i Value Object.

Value Object come record

Modelli i Value Object del dominio come record. La loro uguaglianza strutturale, l'immutabilità e la sintassi concisa si allineano perfettamente ai principi 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); // True

Validazione nei costruttori dei Value Object

Aggiunga un costruttore compatto per convalidare gli invarianti. I Value Object dovrebbero poter essere creati solo in uno stato valido: uno stato non valido deve generare un'eccezione al momento della costruzione.

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 ArgumentException

Entità e Value Object

Le entità hanno un'identità (tracciata tramite un ID nel tempo); i Value Object non hanno identità e sono intercambiabili se i valori corrispondono. Li modelli in modo diverso in 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 value

Value Object con metodi di dominio

I Value Object possono contenere la logica di dominio sotto forma di metodi. Le operazioni restituiscono nuovi Value Object anziché modificare quello esistente.

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);
}

Eventi di dominio come record

Gli eventi di dominio descrivono fatti avvenuti nel dominio. I record sono ideali: sono immutabili, descrittivi e usano l'uguaglianza strutturale, che semplifica i test.

// 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 e contratti API come record

I DTO di richiesta e risposta sono naturalmente Value Object: trasportano dati senza avere un'identità. I record li rendono concisi e immutabili.

// 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);

Memorizzazione dei Value Object in EF Core

Utilizzi le entità owned per rendere persistenti i Value Object in EF Core. Le proprietà di ogni Value Object vengono memorizzate nella tabella dell'entità proprietaria (o in una tabella separata), senza una PK separata.

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 con Value Object

Metta insieme tutti gli elementi: un aggregate root Order che utilizza record per i Value Object e gli eventi di dominio, con tutte le regole di business applicate nei metodi.

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 reale: ID fortemente tipizzati

Utilizzi i record per racchiudere ID primitivi ed evitare di mescolare accidentalmente gli ID di entità diverse: una tecnica DDD comune chiamata "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!

Controllo rapido

Che cosa definisce un Value Object in DDD e perché i record sono una scelta naturale?

Riepilogo: i record nel Domain-Driven Design

Punti chiave:

  • Value Object: definiti dai valori, senza identità — utilizzare i record
  • Entità: hanno un'identità (ID) e uno stato mutabile — utilizzare le classi
  • Convalidare gli invarianti nei costruttori compatti — uno stato non valido non è rappresentabile
  • Eventi di dominio come record: immutabili, confrontabili e autoesplicativi
  • Utilizzare le entità owned di EF Core per rendere persistenti i Value Object
  • Gli ID fortemente tipizzati creati con i record impediscono di mescolare gli ID delle entità
Gratis per iniziare

Impara C# con un tutor IA — gratis

Scrivi ed esegui vero codice nel tuo browser, ricevi aiuto istantaneo da un tutor IA disponibile 24/7, e riprendi da dove hai lasciato sul web o nell'app.

Corsi
93
Lezioni
346

Domande Frequenti

La lezione «Record nel Domain-Driven Design» è gratuita?

Sì — il testo completo di «Record nel Domain-Driven Design» è gratuito qui sul web. Per esercitarvi in modo interattivo (un editor di codice integrato e un tutor IA 24/7) e sbloccare il resto del corso C# Academy, passa a CoddyKit PRO. Il corso C# Academy include 4 lezioni in totale.

Cosa imparerò in «Record nel Domain-Driven Design»?

Modelli oggetti valore, DTO ed eventi di dominio come record per garantire l'immutabilità e migliorare la chiarezza del codice. Eserciti C# Academy con codice pratico che esegui direttamente nel browser, e un tutor IA 24/7 risponde alle tue domande mentre lavori sulla lezione.

Ho bisogno di esperienza per iniziare C# Academy?

Non è richiesta alcuna esperienza precedente. C# Academy su CoddyKit è strutturato per principianti e studenti avanzati, quindi puoi iniziare da qui o dall'inizio e procedere al tuo ritmo. Questa è la lezione 4 di 4.

Quanto tempo richiede la lezione «Record nel Domain-Driven Design»?

La maggior parte delle lezioni CoddyKit richiede circa 5–10 minuti. Ogni lezione è breve e interattiva, quindi fai progressi costanti e riprendi esattamente da dove hai lasciato su web e app.

Posso scrivere ed eseguire codice in questa lezione C# Academy?

Sì. Ogni lezione C# Academy include un editor di codice integrato, quindi scrivi ed esegui codice reale direttamente nel tuo browser e ricevi feedback istantaneo dall'IA — nessuna configurazione locale necessaria.

Tutte le lezioni di questo corso

  1. Tipi record: basi e sintassi
  2. Immutabilità con init e with
  3. Uguaglianza per valore e destrutturazione
  4. Record nel Domain-Driven Design
← Torna a C# Academy