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 #

AspekEntityValue Object
Dianggap sama bilaIdentitas sama, misalnya IdSeluruh nilai sama
IsiBisa berubah sepanjang masa pakaiImmutable, perubahan menghasilkan instance baru
Masa pakaiDilacak dari awal hingga akhir prosesDipakai, dibandingkan, lalu dibuat ulang
ContohPesanan, PelangganUang, 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:

csharp
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:

text
IDR 40000
True
Mata uang kedua nilainya berbeda.

Mari kita bedah bagian-bagian pentingnya:

  1. public record Uang(int Nominal, string MataUang): record memastikan dua Uang dengan nominal dan mata uang sama dianggap sama, sehingga pembandingannya berbasis nilai.
  2. Tambah(Uang lain): aturan domain berjalan di dalam tipe; penjumlahan lintas mata uang ditolak dengan ArgumentException, sehingga pemanggil tidak mungkin membentuk total yang tidak sah.
  3. this with { Nominal = Nominal + lain.Nominal }: hasil penjumlahan berupa instance baru; value object tidak mengubah dirinya sendiri, sesuai sifat immutable.
  4. hargaKopi == new Uang(25000, "IDR"): hasilnya True; kesetaraan mengabaikan alamat object dan hanya melihat nilai.
  5. public override string ToString(): format tampilan disatukan di dalam tipe, sehingga semua pemanggil menampilkan IDR 40000 dengan 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 Tambah tinggal bersama datanya, bukan tersebar di method lain.