Domain-Driven Design di Laravel: Mengapa Kita Butuh Modularitas? (Part 1)
Laravel merupakan web framework PHP yang terkenal dengan pendekatan MVC-nya (Model-View-Controller). Pendekatan/arsitektur MVC ini singkatnya ialah memecah sebuah sistem aplikasi menjadi tiga bagian secara horizontal. Sistem dipecah berdasarkan fungsionalitasnya: Model bertugas mengelola data (operasi CRUD, accessor/mutator, dsb.), View mengelola presentasi/tampilan (HTML templating, styling, interactivity, dsb.), dan Controller mengontrol alur request dan response.
Namun, bagi banyak developer Laravel, pendekatan MVC ini seringkali membawa tantangan tersendiri seiring berkembangnya sebuah aplikasi. Karena aplikasi dipecah secara horizontal, komponen-komponen didalamnya cenderung tightly coupled satu sama lain. Kita mungkin sering menemukan controller yang terlalu gemuk (fat controller) atau model yang terlalu pintar (God Model), yang mana satu bagian kode melakukan terlalu banyak hal atau terlalu bergantung pada bagian lain.
Tentu, ini tidak menjadi masalah apabila skala aplikasi kita kecil, atau sistem kita tidak menuntut modularitas. Akan tetapi, di iklim industri saat ini yang menuntut agility dan perubahan yang cepat, arsitektur yang modular menjadi pertimbangan penting bagi tim. Kita membutuhkan teknik agar perubahan di satu bagian aplikasi tidak merusak bagian lain, dan itulah mengapa kita perlu memikirkan kembali bagaimana kita menentukan rancangan aplikasi, termasuk di ekosistem Laravel yang sudah mature ini.
By the Way, Memang Apa Sih Aplikasi Modular Itu?
Sederhananya, aplikasi yang modular itu dibangun menggunakan beberapa komponen independen yang dapat kita sebut sebagai modul. Setiap modul dapat bekerja secara mandiri tanpa bergantung pada modul lain secara berlebihan.
Analoginya mirip sebuah kendaraan. Sebuah mobil terdiri dari berbagai komponen, seperti mesin, sistem kelistrikan, rem, dan bodi. Setiap komponen ini dirancang untuk bekerja secara independen. Apabila rem blong, mesin mobil tidak serta merta menjadi rusak, karena keduanya adalah komponen yang berbeda. Tentu, mobil tidak akan aman dikendarai, tapi kegagalan di satu bagian bukan berarti meruntuhkan seluruh sistem.
Sama halnya dalam software, jika modul pembayaran mengalami masalah, kita tidak ingin modul user ikut terpengaruh atau bahkan membuat seluruh aplikasi crash.
Kemudian, mengapa kita membutuhkan aplikasi yang modular? Tujuan utamanya adalah maintainability atau kemudahan dalam pemeliharaan sistem. Kita tentu tidak ingin mengubah satu blok kode justru membuat keseluruhan sistem tidak stabil dan menghasilkan bug di mana-mana. Dengan memecah aplikasi menjadi modul-modul yang lebih kecil dan fokus pada satu tanggung jawab, harapannya kita bisa:
- Mempermudah proses pengembangan fitur baru.
- Membuat testing jadi lebih terisolasi.
- Mempermudah proses debugging.
- Meningkatkan produktivitas tim secara keseluruhan karena beberapa developer bisa bekerja paralel pada modul yang berbeda tanpa banyak konflik.
Domain Driven Design
Jadi, bagaimana cara kita mengembangkan aplikasi yang modular? Nah, di sinilah Domain-Driven Design (DDD) hadir.
Domain Driven Design adalah sekumpulan prinsip dalam merancang aplikasi yang menjadikan “domain” atau “business logic” sebagai titik pusat atau inti dari seluruh sistem. Segala hal lain di luar inti domain tersebut dianggap sebagai supporting system, bukan penggerak utama. Karena domain inilah yang “menggerakkan” (drive) keseluruhan sistem, maka model pengembangan ini disebut “domain driven,” atau secara harfiah berarti digerakkan oleh domain.
Yang simple-simple aja bang jelasinnya :<
Bayangkan kita punya aplikasi todo list. Kita bisa mencoba memecahnya menjadi beberapa komponen atau domain, misalnya: User Management, Todo, dan Notifikasi.
Setiap domain ini memiliki keahlian khusus dan hanya fokus pada satu hal. Contohnya, domain User Management adalah pihak yang PALING AHLI dalam mengelola segala sesuatu yang berhubungan dengan pengguna, seperti authentication dan authorization. Oleh karena itu, hanya domain inilah yang berhak mengatur business logic terkait pengguna. Domain lain seperti Todo atau Notifikasi tidak boleh “menyentuh” detail implementasi dari domain User Management. Begitu pula sebaliknya, domain User Management juga tidak boleh “lompat pagar” dengan mengurusi domain lain.
Masing-masing domain ini akan mengelola core business logic mereka sendiri, termasuk bagaimana mereka menyimpan data, hingga presentation layer mereka (misalnya, Web API atau Service API).
Jika divisualisasikan, ini seperti kita memotong sebuah aplikasi secara vertikal. Setiap “potongan vertikal” ini adalah sebuah domain yang mengelola infrastruktur hingga presentation layer mereka sendiri.
Ini kontras dengan arsitektur MVC yang memotong aplikasi secara horizontal. Dalam DDD, sistem akan dipecah secara vertikal berdasarkan domain-domain spesifik. Setelah itu, di dalam masing-masing domain barulah kita bisa memecahnya lagi secara horizontal sesuai dengan layer fungsionalitasnya (misalnya, application layer, domain layer, infrastructure layer, dll.).
Komunikasi Antar Domain
“Bang, bang… Kalo saya mau bikin todo list, kan tetap butuh informasi user yang membuat todo itu, lah terus gimana caranya komunikasi antar domain?”
Nah, pertanyaan ini mejadi kunci keberhasilan Domain-Driven Design. Tujuan utama DDD adalah menghasilkan sistem yang Low Coupling, High Cohesion. Artinya, sebuah domain seharusnya tidak terlalu bergantung (atau tightly coupled) terhadap detail implementasi domain lain. Namun, ketika domain-domain itu bekerja sama, mereka harus menghasilkan perpaduan yang ✨epic✨ sehingga seluruh sistem dapat berjalan dengan baik dan harmonis.
Seperti yang sudah disebutkan sebelumnya, setiap domain memiliki presentation layer-nya sendiri. Layer ini bisa berupa apa saja, misalnya HTML form, REST API, atau service API. Ketika kita berbicara tentang domain yang berada dalam satu aplikasi yang sama, tentu kita tidak memerlukan REST API untuk komunikasi internal, ‘kan?
Di sinilah kita perlu ingat bahwa API bukan hanya REST API. API adalah singkatan dari Application Programming Interface, yang pada intinya adalah “antar muka” yang kita gunakan untuk berinteraksi dengan fungsionalitas di dalamnya.
Setiap domain akan mengeluarkan (expose) service API mereka, misal dalam bentuk sebuah function dengan berbagai parameter. Melalui function atau API inilah, domain-domain lain dapat menggunakan fungsionalitas yang disediakan tanpa perlu tahu detail implementasi di dalamnya.
Misalnya, pada domain Todo, kita membutuhkan informasi user untuk mengambil data todo mereka. Kita cukup memanggil API yang diberikan oleh domain User Management untuk memperoleh User ID. Bagaimana proses User ID itu diperoleh (misal dari database atau cache), domain Todo tidak perlu tahu. Yang domain Todo ketahui hanyalah: “Untuk memperoleh User ID, panggil function ini,” cukup.
Kemudian, umumnya, domain tidak akan mengonsumsi langsung API dari domain lain. Sebagai gantinya, setiap domain akan membuat sebuah “adapter” sebagai middle man. Ini adalah praktik krusial untuk menjaga low coupling!
Bayangkan kita pergi ke luar negeri yang memiliki jenis stop kontak berbeda. Tentu kita tidak mau memotong kabel perangkat kita dan mengganti kepalanya sesuai dengan stop kontak negara itu setiap kali berpindah negara. Kita cukup membeli adapter yang menyesuaikan kepala kabel kita dengan stop kontak tersebut. Apabila kita pergi ke negara lain lagi dengan jenis stop kontak berbeda, kita cukup mengganti adapter-nya saja, bukan kabelnya.
Melalui teknik adapter inilah, low coupling dapat benar-benar tercapai. Perubahan pada service API di satu domain hanya perlu disesuaikan pada adapter di domain yang mengonsumsi API tersebut, bukan pada semua kode yang menggunakan API tersebut. Ini membuat sistem jauh lebih tangguh dan mudah diubah.
Praktek!!!
“Yapping doang mah gampang, kodenya mana bruhh?”
Saya akan mencoba menerapkan prinsip Domain-Driven Design ini dalam sebuah proyek, yaitu sebuah Ticketing System sederhana. Website ini nantinya memungkinkan customer untuk mengajukan laporan/tiket, yang kemudian dapat ditindaklanjuti oleh admin.
Dari sekilas, kita sudah bisa mengidentifikasi beberapa domain yang ada didalamnya: misal domain User Management dan domain Ticketing itu sendiri. Namun, disamping itu, ada beberapa detail yang menarik. Seperti bagaimana kita akan memisahkan admin dashboard dengan customer portal?, atau bagaimana kita menangani user authentication di antara domain-domain tersebut?.
Aplikasi ini saya kembangkan di atas Laravel. Saya tidak ingin melenceng terlalu jauh dari “kultur” atau ekosistem Laravel. Oleh karena itu, saya memilih untuk tidak menggunakan 3rd party library seperti nWidart/laravel-modules yang bagi saya cukup kompleks, dan saya kesulitan untuk memahaminya. Demikian pula dengan library InterNACHI/modular yang menurut saya belum sepenuhnya menjawab kebutuhan kita dalam menghasilkan sistem low coupling, high cohesion antar domain.
Artikel ini adalah Part 1, sebuah pengantar dasar tentang prinsip dan mengapa kita membutuhkan Domain Driven Design. Tentu, perjalanan proyek eksperimen ini akan terus saya perbarui pada part-part selanjutnya, di mana kita akan mulai menyelam ke dalam implementasi dan detail arsitektur.
Terima kasih sudah membaca, dan sampai jumpa di artikel berikutnya! 👋