27 poin oleh GN⁺ 2025-04-24 | 4 komentar | Bagikan ke WhatsApp
  • 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 (Username dan 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 Category dan BlogPost ke 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

 
bus710 2025-04-25

Awalnya seru saat baru ditulis seadanya dan bisa jalan
namun begitu mulai menambahkan pengujian
kita jadi berpikir, kenapa dulu saya membuatnya seperti itu.

 
bungker 2025-04-24

"Saya cuma ingin pisang, tapi yang datang malah seluruh hutan" — ungkapan ini lucu banget.

 
iwanhae 2025-04-24

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...

 
GN⁺ 2025-04-24
Komentar Hacker News
  • Tidak mengizinkan dependensi siklik adalah pilihan desain yang sangat baik saat membangun program berskala besar

    • Ini memaksa pemisahan concern yang tepat
    • Jika dependensi siklik muncul, berarti ada masalah dalam desain, dan artikel tersebut menjelaskan dengan baik cara mengatasinya
    • Terkadang dependensi siklik diselesaikan dengan menggunakan function pointer yang didefinisikan ulang oleh paket lain
    • Akan bagus jika kompiler Go memberikan output yang lebih berguna saat dependensi siklik terbentuk
    • Saat ini, kompiler memberikan daftar semua paket yang terlibat dalam loop, yang bisa cukup panjang, dan biasanya penyebab masalahnya adalah yang terakhir diubah
  • Postingan blog yang luar biasa

    • Situs web ini punya banyak postingan menakjubkan, dan jika Anda suka belajar tentang functional programming, saya sarankan untuk melihatnya
    • tautan
  • Teknik bonus terkait saran "pindahkan ke paket ketiga"

    • Dengan menghasilkan banyak struktur model (SQL, Protobuf, GraphQL, dll.), Anda dapat menetapkan arah yang jelas antar lapisan yang dihasilkan
    • Semua kode yang dihasilkan disediakan ke kode aplikasi sebagai "paket dasar" untuk menyusun semuanya bersama-sama
    • Sebelum teknik ini diterapkan, ada masalah "model mengimpor model secara siklik", tetapi hal itu sepenuhnya hilang setelah penambahan lapisan struktural tambahan
  • Rasanya seperti sedang membaca buku tentang metode terstruktur Yourdon

  • Paket tidak bisa saling mereferensikan secara siklik

    • Sebenarnya, di Go itu memungkinkan dengan menggunakan go:linkname
  • 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

    • Singkatnya, itu juga sebaiknya tidak dilakukan
  • Penjelasan yang keren tentang bagaimana Jerf memandang paket dan menangani dependensi siklik