Pendekatan desain berlapis di Go
(jerf.org)- Bahasa Go secara ketat melarang referensi siklik antarpaket, sehingga secara alami mendorong desain berlapis (layered design)
- Tulisan ini menjelaskan struktur berlapis yang secara wajib dimiliki proyek Go, dan berpendapat bahwa struktur ini sudah cukup valid meski tanpa memaksakan arsitektur tambahan di atasnya
- Saat dependensi siklik muncul, tulisan ini menyajikan strategi refaktorisasi yang konkret dan praktis untuk mengatasinya langkah demi langkah
- Setiap paket dirancang agar memiliki unit fungsi yang bermakna secara mandiri, sehingga menguntungkan untuk pengujian, pemeliharaan, dan pemisahan microservice
- Pada akhirnya, pendekatan ini mencegah masalah umum dalam desain kode nyata berupa "ingin pisang tetapi malah membawa seluruh hutan"
Pendekatan desain berlapis di Go
Prinsip dasar
- Go melarang referensi siklik antarpaket
- Relasi import pada semua program Go harus membentuk graf berarah asiklik (DAG)
- Struktur ini bukan pilihan, melainkan aturan desain yang dipaksakan di level bahasa
Pembentukan layering paket secara otomatis
- Paket internal proyek, selain paket eksternal, dapat secara otomatis dilayerkan berdasarkan kedalaman referensi
- Seperti pada gambar di bawah, di lapisan paling bawah terdapat paket utilitas inti seperti metrics, logging, dan struktur data umum
- Setelah itu, paket-paket tingkat atas membentuk struktur yang menumpuk ke atas sambil makin menggabungkan fungsionalitas
Karakteristik pendekatan desain ini
- Layer bukan didasarkan pada abstraksi hierarkis, melainkan pada arah referensi
- Satu paket dapat merujuk ke banyak paket level lebih rendah
- Pendekatan desain yang sudah ada seperti MVC dan arsitektur heksagonal juga dapat "diterapkan" di atas struktur ini
→ Namun, batasan struktural Go tetap harus dipertimbangkan
Strategi penyelesaian referensi siklik
Saat referensi siklik terjadi, cobalah refaktorisasi dalam urutan berikut:
1. Memindahkan fungsi
- Cara yang paling direkomendasikan
- Analisis secara tepat fungsi yang menyebabkan siklus, lalu pindahkan ke lokasi yang secara logis tepat
- Tidak sering digunakan, tetapi paling meningkatkan kejelasan konseptual
2. Memisahkan fungsi bersama ke paket terpisah
- Pindahkan tipe atau fungsi yang dipakai bersama oleh kedua sisi (
Usernamedan sejenisnya) ke paket ketiga - Pisahkan dengan berani meski paketnya tampak kecil
→ Seiring waktu, kemungkinan besar paket tersebut akan membesar
3. Membuat paket komposisi tingkat atas
- Buat paket ketiga yang menggabungkan dua paket yang saling bersiklus
- Contoh: pisahkan dependensi dua arah
CategorydanBlogPostke paket tingkat atas
→ Paket level bawah tetap berupa dumb struct, dan fungsi sebenarnya dikomposisikan di paket tingkat atas
4. Memperkenalkan interface
- Gantikan dependensi dengan interface yang hanya memiliki method yang diperlukan oleh struct atau fungsi
- Menghapus dependensi yang tidak perlu dan meningkatkan kemudahan pengujian
- Namun, jika digunakan berlebihan, desain justru bisa menjadi lebih rumit
5. Menyalin (Copy)
- Jika target dependensi sangat kecil, cukup salin saja untuk digunakan
- Ini mungkin tampak seperti pelanggaran DRY, tetapi sering kali justru membantu memperjelas desain
6. Menggabungkan menjadi satu paket
- Jika semua cara di atas tidak memungkinkan, gabungkan kedua paket
- Masih dapat diterima selama paketnya tidak menjadi terlalu besar
→ Namun, hindari penggabungan secara serampangan dan putuskan dengan hati-hati
Keunggulan praktis pendekatan desain ini
- Setiap paket memiliki unit fungsi yang bermakna dengan sendirinya dan dapat diuji secara mandiri
- Karena referensi di dalam paket dibatasi, setiap paket dapat dipahami tanpa harus memahami seluruh kode
- Menghindari keterhubungan dependensi menyeluruh yang tidak diinginkan (= masalah hutan), dan mendorong penulisan kode yang hanya memakai yang diperlukan
- Mudah diekstrak saat memisahkan microservice
→ Sebagian besar dependensi sudah didefinisikan dengan jelas
Kesimpulan
- Batasan desain paket di Go bukan sekadar batasan yang merepotkan, melainkan mekanisme yang mendorong desain yang baik
- Bahkan tanpa arsitektur khusus, struktur referensi antarpaket saja sudah cukup untuk mewujudkan desain yang kokoh
- Analisis yang cermat dan strategi refaktorisasi terhadap referensi siklik tetap relevan tidak hanya untuk Go, tetapi juga untuk bahasa lain
4 komentar
Awalnya seru saat baru ditulis seadanya dan bisa jalan
namun begitu mulai menambahkan pengujian
kita jadi berpikir, kenapa dulu saya membuatnya seperti itu.
"Saya cuma ingin pisang, tapi yang datang malah seluruh hutan" — ungkapan ini lucu banget.
Salah satu hal yang paling sulit saat mengembangkan dengan Spring sepertinya adalah dependensi sirkular..
Rasa frustrasi ketika komponen saling menginisialisasi tanpa henti lalu crash karena kebocoran memori itu benar-benar menyebalkan...
Komentar Hacker News
Tidak mengizinkan dependensi siklik adalah pilihan desain yang sangat baik saat membangun program berskala besar
Postingan blog yang luar biasa
Teknik bonus terkait saran "pindahkan ke paket ketiga"
Rasanya seperti sedang membaca buku tentang metode terstruktur Yourdon
Paket tidak bisa saling mereferensikan secara siklik
Mengingatkan pada konsep konkret randomizer
Hal yang menarik dari Golang adalah bahwa dependensi siklik tidak bisa ada di tingkat paket, tetapi bisa ada di go.mod
Penjelasan yang keren tentang bagaimana Jerf memandang paket dan menangani dependensi siklik