Record dalam Domain-Driven Design
Modelkan objek nilai, DTO, dan peristiwa domain sebagai record untuk menegakkan immutability dan memperjelas kode.
Record dalam Domain-Driven Design adalah pelajaran C# Academy gratis di CoddyKit. Ini adalah pelajaran 4 dari 4. Kamu bisa membaca pelajaran lengkapnya di bawah secara gratis — lalu praktikkan langsung di browser dengan editor kode bawaan dan tutor AI 24/7. Ini adalah bagian dari jalur belajar C# Academy, dan progresmu tersinkronisasi di web dan aplikasi CoddyKit. Kursus C# Academy mencakup 4 pelajaran total.
DDD dan Objek Nilai
Dalam Domain-Driven Design (DDD), Objek Nilai ditentukan oleh propertinya, bukan identitasnya. Dua nilai Money(100, "USD") dapat saling menggantikan. Record adalah representasi C# yang sempurna untuk objek nilai.
Objek Nilai sebagai Record
Modelkan objek nilai domain sebagai record. Kesetaraan struktural, ketidakberubahan, dan sintaksisnya yang ringkas sangat selaras dengan prinsip-prinsip 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); // TrueValidasi dalam Konstruktor Objek Nilai
Tambahkan konstruktor ringkas untuk memvalidasi invarian. Objek nilai seharusnya hanya dapat dibuat dalam keadaan yang valid — keadaan yang tidak valid harus memunculkan pengecualian saat konstruksi.
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 ArgumentExceptionEntitas vs Objek Nilai
Entitas memiliki identitas (dilacak berdasarkan ID dari waktu ke waktu); Objek Nilai tidak memiliki identitas dan dapat saling menggantikan jika nilainya sama. Modelkan keduanya secara berbeda dalam 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 valueObjek Nilai Kaya dengan Metode Domain
Objek nilai dapat berisi logika domain sebagai metode. Operasi mengembalikan objek nilai baru, bukan mengubah objek yang sudah ada.
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);
}Peristiwa Domain sebagai Record
Peristiwa domain menjelaskan hal-hal yang telah terjadi dalam domain. Record sangat cocok: bersifat tidak berubah, deskriptif, dan menggunakan kesetaraan struktural yang menyederhanakan pengujian.
// 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 dan Kontrak API sebagai Record
DTO permintaan/respons secara alami merupakan objek nilai — DTO membawa data tanpa identitas. Record membuatnya ringkas dan tidak dapat diubah.
// 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);Menyimpan Objek Nilai dalam EF Core
Gunakan entitas milik untuk menyimpan objek nilai dalam EF Core. Properti setiap objek nilai disimpan di tabel pemiliknya (atau di tabel terpisah) tanpa PK terpisah.
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);
});Akar Agregat dengan Objek Nilai
Gabungkan semuanya: akar agregat Order yang menggunakan record untuk objek nilai dan peristiwa domain, dengan semua aturan bisnis diterapkan dalam metode.
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));
}
}Praktik Nyata: ID Bertipe Kuat
Gunakan record untuk membungkus ID primitif dan mencegah tercampurnya ID entitas yang berbeda secara tidak sengaja — teknik DDD umum yang disebut "ID Bertipe Kuat".
// 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!Pemeriksaan Singkat
Apa yang mendefinisikan Objek Nilai DDD, dan mengapa record sangat cocok untuknya?
Ringkasan: Record dalam Domain-Driven Design
Inti penting:
- Objek Nilai: ditentukan oleh nilai, tanpa identitas — gunakan record
- Entitas: memiliki identitas (ID) dan keadaan yang dapat berubah — gunakan class
- Validasi invarian dalam konstruktor ringkas — keadaan yang tidak valid tidak dapat direpresentasikan
- Peristiwa domain sebagai record: tidak dapat diubah, dapat dibandingkan, dan mendokumentasikan dirinya sendiri
- Entitas milik EF Core untuk menyimpan objek nilai
- ID bertipe kuat menggunakan record mencegah tercampurnya ID entitas
Pertanyaan yang Sering Diajukan
Apakah pelajaran “Record dalam Domain-Driven Design” gratis?
Ya — teks lengkap “Record dalam Domain-Driven Design” gratis dibaca di sini di web. Untuk praktiknya secara interaktif (editor kode bawaan dan tutor AI 24/7) dan buka sisa kursus C# Academy, upgrade ke CoddyKit PRO. Kursus C# Academy mencakup 4 pelajaran total.
Apa yang akan aku pelajari di “Record dalam Domain-Driven Design”?
Modelkan objek nilai, DTO, dan peristiwa domain sebagai record untuk menegakkan immutability dan memperjelas kode. Kamu berlatih C# Academy dengan kode praktik yang langsung kamu jalankan di browser, dan tutor AI 24/7 menjawab pertanyaanmu saat kamu mengerjakan pelajaran ini.
Apakah aku perlu pengalaman untuk memulai C# Academy?
Tidak diperlukan pengalaman sebelumnya. C# Academy di CoddyKit dirancang untuk pemula hingga pelajar tingkat lanjut, jadi kamu bisa memulai di sini atau dari awal dan belajar sesuai kecepatan kamu sendiri. Ini adalah pelajaran 4 dari 4.
Berapa lama pelajaran “Record dalam Domain-Driven Design” memakan waktu?
Sebagian besar pelajaran CoddyKit memakan waktu sekitar 5–10 menit. Setiap pelajaran ringkas dan interaktif, jadi kamu membuat kemajuan stabil dan melanjutkan dari tempat kamu tinggalkan di web dan aplikasi.
Bisakah aku menulis dan menjalankan kode dalam pelajaran C# Academy ini?
Ya. Setiap pelajaran C# Academy menyertakan editor kode bawaan, jadi kamu menulis dan menjalankan kode nyata langsung di browser dan mendapatkan umpan balik AI instan — tidak diperlukan penyiapan lokal.
Semua pelajaran dalam kursus ini
- Tipe Record: Dasar & Sintaks
- Immutability dengan init & with
- Kesetaraan Nilai & Dekonstruksi
- Record dalam Domain-Driven Design