Domain-driven design (DDD) membedakan dua jenis data di dalam aplikasi. Entity dibedakan lewat identitas: dua pesanan dengan Id berbeda tetap dua pesanan, walau isinya kebetulan sama. Value object dibedakan lewat nilai saja: dua uang sebesar 25000 rupiah adalah dua hal yang setara, tidak peduli di variable mana keduanya disimpan. Value object yang dirancang dengan baik membuat aturan domain pindah dari method lepas ke dalam tipe yang memang memilikinya.
Konsep Entity dan Value Object #
| Aspek | Entity | Value Object |
|---|---|---|
| Dianggap sama bila | Identitas sama, misalnya Id | Seluruh nilai sama |
| Isi | Bisa berubah sepanjang masa pakai | Immutable, perubahan menghasilkan instance baru |
| Masa pakai | Dilacak dari awal hingga akhir proses | Dipakai, dibandingkan, lalu dibuat ulang |
| Contoh | Pesanan, Pelanggan | Uang, Alamat, RentangTanggal |
Membangun Value Object dengan Record #
Record adalah bahan yang tepat untuk value object karena kesetaraannya membandingkan isi. Contoh berikut membangun value object Uang dengan aturan domain di dalamnya:
public record Uang(int Nominal, string MataUang)
{
public Uang Tambah(Uang lain)
{
if (lain.MataUang != MataUang)
{
throw new ArgumentException("Mata uang kedua nilainya berbeda.");
}
return this with { Nominal = Nominal + lain.Nominal };
}
public override string ToString() => $"{MataUang} {Nominal}";
}
Uang hargaKopi = new Uang(25000, "IDR");
Uang hargaRoti = new Uang(15000, "IDR");
Uang total = hargaKopi.Tambah(hargaRoti);
Console.WriteLine(total);
Console.WriteLine(hargaKopi == new Uang(25000, "IDR"));
Uang dolar = new Uang(10, "USD");
try
{
hargaKopi.Tambah(dolar);
}
catch (ArgumentException pesan)
{
Console.WriteLine(pesan.Message);
}Output yang Dihasilkan:
IDR 40000
True
Mata uang kedua nilainya berbeda.Mari kita bedah bagian-bagian pentingnya:
public record Uang(int Nominal, string MataUang): record memastikan duaUangdengan nominal dan mata uang sama dianggap sama, sehingga pembandingannya berbasis nilai.Tambah(Uang lain): aturan domain berjalan di dalam tipe; penjumlahan lintas mata uang ditolak denganArgumentException, sehingga pemanggil tidak mungkin membentuk total yang tidak sah.this with { Nominal = Nominal + lain.Nominal }: hasil penjumlahan berupa instance baru; value object tidak mengubah dirinya sendiri, sesuai sifat immutable.hargaKopi == new Uang(25000, "IDR"): hasilnyaTrue; kesetaraan mengabaikan alamat object dan hanya melihat nilai.public override string ToString(): format tampilan disatukan di dalam tipe, sehingga semua pemanggil menampilkanIDR 40000dengan format yang sama.
Ciri Value Object yang Baik #
- Immutable: seluruh property hanya diisi saat pembuatan; perubahan dikembalikan sebagai instance baru.
- Menjaga validasi: aturan domain diperiksa di constructor atau method milik tipe itu sendiri.
- Setara berdasarkan isi: dua instance dengan nilai sama saling menggantikan.
- Memiliki perilaku: operasi domain seperti
Tambahtinggal bersama datanya, bukan tersebar di method lain.