Setelah migrasi ke Go, ada satu warisan yang cukup bikin saya geleng-geleng: semua repository numpuk di dua folder besar, repository dan usecase. Isinya campur aduk, dari alert dan billing sampai geofence dan fuel. Secara teknis sistemnya jalan. Tapi kalau mau cari satu rumah di kota sebesar itu, saya harus hafal semua jalan.
Di episode ini saya merapikan strukturnya menjadi 14 modul domain, sambil menjaga endpoint tetap melayani production. Penggunanya puluhan ribu, jadi tidak ada tombol jeda yang bisa saya pencet sebentar.
Kenapa Bukan Microservices #
Mungkin ada yang bertanya: kalau domainnya mau dipisah, kenapa tidak langsung pakai microservices?
Tim kami kecil. Microservices juga bukan cuma soal folder atau diagram, tapi menambah urusan infrastruktur. Tiap layanan perlu di-deploy dan dipantau, lalu komunikasi antar-layanan yang tadinya tinggal panggil fungsi jadi urusan jaringan. Buat tim kami, enam server tambahan untuk merapikan folder rasanya seperti beli rumah baru cuma untuk menyimpan kamar kosong.
Saya tidak butuh proses yang terpisah. Yang saya butuhkan batas tanggung jawab yang jelas. Makanya kami memilih modular monolith: satu binary dan satu deployment, tapi domain di dalamnya punya batas masing-masing.
Modul Itu Rumah dengan Satu Pintu #
Aturan modulnya sederhana:1 satu folder untuk satu domain, misalnya geofence, lalu repository dan usecase tinggal di sana. Folder api jadi pintu keluarnya, berisi interface yang boleh dipakai dari luar. Batasnya dijaga compiler, bukan sekadar janji di README.
Dinding compile-time itu kuncinya. Aturan modul yang hanya tertulis di README mudah dilanggar saat sprint sibuk. Namun, jika repo internal modul tidak bisa diimpor dari luar folder-nya, build langsung merah. Pelanggaran arsitektur jadi sesuatu yang tidak bisa di-commit, bukan sekadar sesuatu yang harus diingat.
Sensus Dulu, Pindah Kemudian #
Sebelum refactoring, saya mulai dengan menghitung siapa memakai apa. Ini bagian yang gampang dilewati kalau sudah tidak sabar ingin memindahkan folder.
Untuk modul terbesar, device, saya membuat dokumen sensus sebelum menyentuh satu baris kode. Hasilnya mengejutkan sendiri: 51 file di luar modul mengonsumsi repo-nya, repo keluarga device menawarkan 321 method exported… dan yang benar-benar dipanggil dari luar cuma 100.2 Lebih dari dua pertiga stoknya tidak pernah diminta. Bahkan satu repositori “dewa” berisi 122 method, yang terpakai 49.
Sensus itu juga punya jebakan. Saya menghitung konsumen lewat pencarian nama, tetapi akhiran “-device” ternyata muncul di banyak tempat: repo shared-link-device dan user-group-device bukan bagian keluarga device. Satu repo lain punya angka di namanya, sehingga pola pencarian huruf-saja melewatkannya. Saya jadi belajar untuk ikut memeriksa cara sensusnya dibuat. Kalau tidak, hasilnya bisa kelihatan meyakinkan padahal yang dihitung keliru.
Pindahan yang Byte-Identik #
Di sini saya pakai lagi prinsip dari episode keempat, kali ini untuk folder sendiri: pindahan bukan waktunya beres-beres sekalian.
Aturan kerjanya: file repo dipindah ke modul tanpa mengubah satu byte pun, Git sampai mengakuinya sebagai pindahan 100% identik. Yang diubah hanya peta pintunya: interface di folder api, adapter yang meneruskannya, dan import path para konsumen. Kalau diff menunjukkan apa pun selain pindahan dan penggantian nama, itu tanda ada yang selingkuh dari rencana.
Kenapa saya repot-repot menjaga pindahannya tetap identik? Review-nya jadi lebih ringan. Yang diperiksa bukan lagi apakah logika barunya benar, cukup memastikan memang tidak ada yang berubah. Puluhan ribu baris bisa pindah alamat tanpa harus dibaca ulang satu per satu. Godaan seperti “sekalian ganti nama variabel, ah” saya tahan dulu. Satu PR, satu urusan.
Ragam Modul Itu Wajar #
Tidak semua modul dirapikan dengan resep sama. Modul geofence saya seal paling ketat: wrapper delegasi era lama dihapus, puluhan method mati dibersihkan, dan kode penanda error-nya diekspor lewat pintu api supaya konsumen bisa memeriksanya tanpa mengintip ke dalam rumah. Modul sharing punya catatan tersendiri: inilah modul pertama yang perlu membaca config aplikasi di pintunya.
Sedangkan modul device, yang terbesar, saya sengaja biarkan pintunya terbuka untuk sementara: repos-nya masih bisa diimpor langsung, dengan rencana yang tertulis jelas untuk seal menyusul. Ini pengecualian sadar, seperti cerita parity di episode keempat: dogma itu untuk buku pelajaran; proyek hidup butuh pengecualian yang tercatat.
Cache Tanpa Redis #
Soal cache, kami memakai cache in-process, ristretto, bukan Redis. Kantor memang tidak menjalankan Redis, dan sepertinya tidak ada yang mau menambah service hanya untuk cache. Ini pas dengan cara deploy kami: satu binary, geser, jalan. Cache yang ikut mati bersama prosesnya tidak masalah karena bukan sumber kebenaran, cuma akselerator. Kalau suatu hari beberapa instance perlu berbagi cache, ya keputusan ini bisa ditinjau lagi.
Utang yang Tersisa #
Masih ada utang teknis yang belum selesai, seperti yang saya ceritakan di episode kelima. Beberapa repo di dalam modul masih membaca tabel domain lain lewat SQL langsung, dan dua modul belum ter-seal. Saya sudah mencatat semuanya. Nanti dikerjakan satu per satu, kota ini tidak harus selesai direnovasi malam ini juga.
Catatan Saya #
- Sensus sebelum pindah. Angka konsumen dan method yang benar-benar dipakai itu yang menentukan bentuk pintu modul, bukan tebakan, bukan selera.
- Dinding yang dijaga compiler lebih kuat daripada kesepakatan verbal. Aturan arsitektur yang tidak bisa dilanggar tanpa build merah adalah satu-satunya aturan yang bertahan di sprint sibuk.
- Pindahan byte-identik membuat review murah. Pisahkan “memindahkan” dari “memperbaiki”, campurkannya, dan keduanya sama-sama jadi susah dicek.
- Pengecualian antar modul itu wajar, asal sadar dan tercatat. Seragam sempurna itu indah di diagram; proyek hidup butuh pintu yang kebijakannya jelas.
Di episode berikutnya, saya keluar dari dashboard dan ikut paket TCP dari tracker GPS sampai masuk menjadi baris di database time-series.
Sekian. Salam.