Menyempurnakan dan Menumbuhkan Model Domain
Setelah kita membangun fondasi Domain Model, lengkap dengan Entity, Value Object, Aggregate, Repository, dan kawan-kawan, muncul pertanyaan besar berikutnya: “Bagaimana model domain kita tetap relevan ketika bisnis berubah?”
Di dunia nyata, bisnis tidak pernah diam. Aturan baru muncul, prioritas berubah, bahkan istilah yang dulu dianggap jelas bisa bergeser maknanya. Maka, Domain Model pun harus tumbuh dan berevolusi. Bab ini membahas bagaimana kita bisa menyempurnakan model tanpa membuat sistem runtuh.
Model Domain Bukan Arsitektur Sekali Jadi
Kesalahan umum dalam menerapkan DDD adalah menganggap domain model sebagai desain permanen. Padahal, model itu seharusnya hidup dan berubah mengikuti pemahaman baru tentang domain.
Khononov (2022) menekankan bahwa DDD bukan tentang mencari model yang sempurna di awal, melainkan mendekati pemahaman yang lebih baik seiring waktu. Setiap diskusi dengan ahli domain, setiap bug, dan setiap fitur baru bisa memperkaya model kita.
Bayangkan model domain seperti peta kota. Saat pertama kali dibuat, mungkin hanya berisi jalan utama. Tapi seiring waktu, kita tambahkan gang kecil, lampu merah, dan area baru — bukan karena peta awal salah, tapi karena pengetahuan kita bertambah.
Proses Refinement: Dari Model Kasar ke Model Tajam
1. Mulai dari Model Kasar
Pada awal proyek, buatlah model sederhana dulu — tidak masalah kalau belum lengkap. Fokus pada pemahaman dasar: apa saja konsep utama dan bagaimana mereka berhubungan.
Misalnya kita membangun sistem untuk pengiriman paket. Model awal bisa sesederhana ini:
struct Package {
id: u64,
status: String,
}
impl Package {
fn deliver(&mut self) {
self.status = "Delivered".to_string();
}
}
Masih sederhana, tapi cukup untuk memulai percakapan dengan ahli domain.
2. Kembangkan Melalui Umpan Balik
Setiap kali bisnis atau stakeholder memberi masukan (“Bagaimana kalau pelanggan bisa menjadwalkan ulang pengiriman?”), kita ubah model agar mencerminkan realitas baru itu.
enum DeliveryStatus {
Pending,
InTransit,
Delivered,
Rescheduled(String), // alasan atau tanggal baru
}
struct Package {
id: u64,
status: DeliveryStatus,
}
Model berkembang mengikuti kebutuhan bisnis, bukan sebaliknya. Perubahan ini adalah bentuk refinement, bukan “refactor tanpa arah”.
3. Evaluasi Bahasa Umum (Ubiquitous Language)
Setiap istilah di model harus sama artinya bagi semua pihak, engineer, product owner, dan ahli domain.
Kalau tim mulai bilang “Shipment” di tempat yang seharusnya “Package”, itu tanda bahasa umum mulai kabur. Periksa model, ubah nama kelas, fungsi, dan variabel agar kembali konsisten dengan istilah bisnis yang terbaru.
Inilah kenapa DDD mendorong kode yang “bercerita seperti domain”-nya.
Pola Umum dalam Evolusi Model
1. Split dan Merge Aggregate
Saat sistem bertumbuh, aggregate yang dulu terasa ideal bisa jadi terlalu besar. Kita mungkin perlu memisahkannya agar tidak menimbulkan transaction contention atau performance bottleneck.
Misalnya awalnya kita punya Order
yang juga menyimpan Payment
dan Shipment
di dalamnya. Tapi seiring waktu, tim pembayaran dan pengiriman berkembang terpisah.
Maka kita pisahkan menjadi tiga aggregate dengan batas yang jelas.
// Awalnya semua di Order
struct Order {
id: u64,
payment_status: String,
shipment_status: String,
}
// Setelah displit
struct Order {
id: u64,
}
struct Payment {
order_id: u64,
status: String,
}
struct Shipment {
order_id: u64,
tracking_code: String,
}
Kini setiap tim bisa berinovasi di domain-nya tanpa saling menunggu.
2. Mengubah Entity menjadi Value Object (atau sebaliknya)
Kadang sebuah objek awalnya dianggap penting secara identitas, tapi belakangan ternyata tidak.
Contoh: pada tahap awal, kita buat Address
sebagai Entity
karena kita pikir pengguna bisa memperbaruinya dan kita perlu menyimpannya secara unik. Tapi kemudian kita sadar bahwa Address
hanya bagian dari Order
dan tidak pernah digunakan secara terpisah.
Maka kita ubah menjadi Value Object
.
#[derive(Clone, PartialEq, Eq)]
struct Address {
street: String,
city: String,
postal_code: String,
}
Sebaliknya, jika suatu saat kita butuh melacak alamat pelanggan secara historis, Address
bisa dipromosikan lagi menjadi Entity
.
3. Menambah Domain Event untuk Dekoupling
Ketika sistem makin besar, modul-modul akan saling tergantung. Untuk mencegah kekacauan, kita bisa menambahkan Domain Event agar komunikasi antarmodul tidak saling menempel.
struct OrderCreated {
order_id: u64,
}
struct SendEmailOnOrderCreated;
impl SendEmailOnOrderCreated {
fn handle(event: &OrderCreated) {
println!("Email konfirmasi untuk order {} dikirim!", event.order_id);
}
}
Dengan event, Order
tidak perlu tahu cara mengirim email, dan modul notifikasi bisa bereaksi secara independen. Ini membuat sistem lebih fleksibel dan siap tumbuh tanpa coupling berlebihan.
Evolusi yang Terarah, Bukan Reaktif
Menumbuhkan model domain bukan berarti “sering ganti struktur kode”. Tujuannya bukan perubahan semata, tapi perubahan yang memperdalam pemahaman kita terhadap domain.
Langkah idealnya seperti ini:
Temukan masalah di dunia nyata. Misalnya pengguna bingung dengan istilah “shipment” vs “delivery”.
Diskusikan dengan ahli domain. Pahami maknanya dalam konteks bisnis.
Perbarui bahasa umum. Ubah nama kelas atau entitas agar konsisten dengan istilah baru.
Refactor model. Pisahkan, gabungkan, atau ubah struktur sesuai pemahaman baru.
Uji dan validasi. Pastikan perubahan tidak merusak perilaku bisnis yang sudah benar.
Jika perubahan dilakukan lewat percakapan yang intens antara engineer dan ahli domain, model domain akan terus “mendekati kenyataan” seiring waktu.
Kesalahan Umum Saat Model Berevolusi
Refactor teknis tanpa konteks bisnis. Mengubah struktur hanya karena “terlihat lebih bersih”, padahal maknanya makin kabur.
Terlalu cepat memecah aggregate. Split boleh dilakukan, tapi pastikan memang ada kebutuhan bisnis atau beban transaksi nyata.
Mengabaikan bahasa umum. Nama-nama di kode harus tetap sejalan dengan istilah di dunia nyata, bukan dengan preferensi pribadi.
Tidak mencatat keputusan. Setiap perubahan besar di domain model sebaiknya punya alasan bisnis yang terdokumentasi.
Menumbuhkan Model Secara Aman
Dalam praktik modern, kita bisa menggunakan pendekatan seperti:
Feature toggles, untuk mengaktifkan model baru tanpa mengganggu produksi.
Event sourcing, agar perubahan model tidak kehilangan jejak historis.
Migration scripts, untuk memindahkan data lama ke struktur baru dengan aman.
DDD tidak menentang perubahan, ia justru mengajarkan cara mengelolanya dengan sadar.
Akhirnya: Model yang Hidup
Domain model yang baik bukan yang paling indah diagramnya, tapi yang tetap bernapas bersama bisnisnya.
Ketika bisnis berubah, model pun ikut beradaptasi. Ketika istilah baru muncul, kode pun ikut menyesuaikan. Dan ketika engineer dan ahli domain berbicara, mereka bicara dalam bahasa yang sama, bukan lagi sekadar query, tapi makna.
Itulah esensi menghadapi kompleksitas: Bukan melawannya dengan abstraksi berlebihan, tetapi menjinakkannya dengan pemahaman yang terus bertumbuh.
Referensi
Khononov, V. (2022). Learning Domain-Driven Design: Aligning Software Architecture and Business Strategy. O’Reilly Media.