Kita sudah mengenal pemisahan tanggung jawab, layering, Clean Architecture, dependency injection, domain modeling, modular monolith, dan event-driven design. Momen paling rawan kesalahan justru datang setelah semua itu: tergoda memakai semuanya sekaligus di proyek pertama. Arsitektur adalah alat, bukan tujuan — pilihan yang tepat bergantung pada ukuran tim, kompleksitas domain, dan kebutuhan skalabilitas yang nyata. Chapter penutup section ini merangkum pertanyaan penentu, matriks keputusan, dan jalur evolusi bertahap agar Anda memilih secara proporsional, bukan mengikuti tren.
Tiga Pertanyaan Penentu #
Sebelum menyebut nama arsitektur mana pun, jawab tiga pertanyaan ini dengan jujur:
- Berapa ukuran timnya? Satu developer bisa menjaga seluruh konteks di kepalanya; dua belas developer butuh batas kepemilikan kode yang tegas agar tidak saling menginjak.
- Seberapa kompleks domainnya? Aplikasi CRUD dengan lima tabel punya aturan bisnis tipis; sistem perbankan atau logistik hidup dari aturan yang berubah sering dan butuh pusat.
- Apakah komponen benar-benar perlu rilis dan skala terpisah? Tim berbeda dengan jadwal rilis berbeda adalah alasan kuat; “kayaknya keren” bukan alasan.
Jawaban atas ketiganya menentukan seberapa banyak struktur yang layak dibayar. Prinsip YAGNI (you aren’t gonna need it) berlaku keras di sini: setiap lapisan, interface, dan modul yang tidak menyederhanakan pekerjaan hari ini adalah beban yang harus dirawat tanpa memberi imbal hasil.
Matriks Keputusan #
| Kondisi | Arsitektur yang cukup | Tanda perlu naik kelas |
|---|---|---|
| 1-2 developer, domain sederhana | Flat: satu package, fungsi terpisah | File main.go melewati ~500 baris |
| Tim kecil, satu area bisnis | Layered: handler, service, repository | Service mulai saling memanggil liar |
| Domain kaya, aturan bisnis padat | Layered + domain model yang hidup | Tim berbeda berebut satu folder service |
| Banyak area, tim 8+ developer | Modular monolith dengan kontrak interface | Ada modul yang butuh rilis/skala sendiri |
| Kebutuhan rilis terpisah terbukti nyata | Ekstraksi modul menjadi service | — |
Perhatikan kolom kanan: naik kelas dipicu oleh rasa sakit yang terukur, bukan oleh ambang jumlah baris semata. Diagram berikut merangkum alurnya sebagai keputusan berurutan:
Biaya Arsitektur Harus Diakui #
Setiap tingkat struktur dibayar dengan sesuatu. Layered menambah jumlah file dan test boilerplate. Clean Architecture menambah interface dan DTO ketika domain memang tidak serumit itu. Microservice membayar paling mahal: jaringan, distributed transaction, observability lintas service, dan on-call yang lebih rumit — biaya yang hanya imbang oleh kemandirian tim yang nyata. Ungkapan Rob Pike, “clear is better than clever”, berlaku ganda di arsitektur: struktur yang bisa dijelaskan ke anggota tim baru dalam lima belas menit mengalahkan struktur yang elegan tetapi butuh sehari untuk dipahami.
Demo: Alat Bantu Keputusan Sederhana #
Berkas pilih-demo.go menerjemahkan tiga pertanyaan tadi menjadi skor sederhana. Program ini bukan oracle — angkanya kasar — tetapi bentuk programnya sendiri mencontohkan prinsip section ini: logika keputusan yang bisa dibaca, diuji, dan diganti ketika asumsinya berubah:
package main
import "fmt"
type Kebutuhan struct {
UkuranTim int // jumlah developer aktif
KompleksitasDomain int // skala 0-10
DeployIndependen bool // perlu rilis terpisah per komponen?
}
func Skor(k Kebutuhan) int {
return k.UkuranTim*2 + k.KompleksitasDomain
}
func Rekomendasi(k Kebutuhan) string {
switch {
case k.UkuranTim <= 2 && k.KompleksitasDomain <= 3:
return "flat: satu package main, fungsi dipisah per file"
case Skor(k) <= 15 && !k.DeployIndependen:
return "layered: handler, service, repository"
case Skor(k) <= 30 && !k.DeployIndependen:
return "modular monolith: satu binary, modul per area di internal/"
default:
return "evaluasi microservice: hanya bila deploy independen benar-benar dibutuhkan"
}
}
func main() {
profil := []struct {
nama string
keb Kebutuhan
}{
{"proyek belajar pribadi", Kebutuhan{UkuranTim: 1, KompleksitasDomain: 2}},
{"aplikasi internal perusahaan", Kebutuhan{UkuranTim: 4, KompleksitasDomain: 6}},
{"platform multi-tim", Kebutuhan{UkuranTim: 12, KompleksitasDomain: 8, DeployIndependen: true}},
}
for _, p := range profil {
fmt.Printf("%-30s -> %s\n", p.nama, Rekomendasi(p.keb))
}
}Jalankan program dengan perintah berikut dari direktori chapter:
go run pilih-demo.goOutput yang Dihasilkan:
proyek belajar pribadi -> flat: satu package main, fungsi dipisah per file
aplikasi internal perusahaan -> layered: handler, service, repository
platform multi-tim -> evaluasi microservice: hanya bila deploy independen benar-benar dibutuhkanMari kita telaah bagian-bagian penting program di atas:
- Baris 5-9 (
type Kebutuhan struct ...): asumsi keputusan dinyatakan eksplisit sebagai struct. Ketika tim menambah faktor lain (misalnya regulasi data), struct ini — bukan seluruh program — yang diperluas. - Baris 11-13 (
func Skor ...): bobot keputusan terpusat di satu fungsi yang mudah dibedah. Pembaca langsung melihat bahwa ukuran tim diberi bobot dua kali kompleksitas domain. - Baris 15-26 (
func Rekomendasi ...): urutancaseadalah urutan keputusan: sederhana dulu, kompleks belakangan.DeployIndependenbertindak sebagai gerbang terakhir sebelum microservice dipertimbangkan. - Baris 33-35 (
profil := ...): tiga profil berurutan dari kecil ke besar. Ketiganya keyed literal sehingga pembaca tahu nilai mana mengisi kolom mana. - Baris 38 (
fmt.Printf ...): output satu baris per profil. Bila Anda mengubah bobotSkor, jalankan ulang program dan lihat rekomendasi mana yang bergeser.
Jalan Praktik di Course Ini #
Seluruh jalur course ini sengaja disusun mengikuti kurva evolusi yang sama. Anda membangun fondasi bahasa di Dasar Bahasa Go , membentuk tipe dan perilaku di Struct dan Method serta Interface , mengelola kegagalan di Error Handling , lalu naik ke aplikasi lewat HTTP dan Networking dan Database dan SQL . Proyek REST API dengan PostgreSQL kemudian merangkai semuanya dalam arsitektur berlapis yang utuh — titik mulai paling realistis untuk sebagian besar aplikasi nyata. Dari sana, pola lanjutan menanti di Design Patterns , section tepat setelah arsitektur ini, tempat idiom-idiom Go yang kita pakai sepanjang section dibedah lebih sistematis.
Arsitektur terbaik bukan yang paling banyak istilahnya, melainkan yang membuat tim Anda hari ini mengirim fitur dengan percaya diri — dan masih bisa berubah besok tanpa rasa takut.
Referensi #
- Use cases for Go — ragam skala aplikasi yang realistis untuk Go.
- Effective Go — kesederhanaan dan kejernihan sebagai nilai utama bahasa.
Berkas Sumber
Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.
pilih-demo.go
Unduh berkaspackage main
import "fmt"
type Kebutuhan struct {
UkuranTim int // jumlah developer aktif
KompleksitasDomain int // skala 0-10
DeployIndependen bool // perlu rilis terpisah per komponen?
}
func Skor(k Kebutuhan) int {
return k.UkuranTim*2 + k.KompleksitasDomain
}
func Rekomendasi(k Kebutuhan) string {
switch {
case k.UkuranTim <= 2 && k.KompleksitasDomain <= 3:
return "flat: satu package main, fungsi dipisah per file"
case Skor(k) <= 15 && !k.DeployIndependen:
return "layered: handler, service, repository"
case Skor(k) <= 30 && !k.DeployIndependen:
return "modular monolith: satu binary, modul per area di internal/"
default:
return "evaluasi microservice: hanya bila deploy independen benar-benar dibutuhkan"
}
}
func main() {
profil := []struct {
nama string
keb Kebutuhan
}{
{"proyek belajar pribadi", Kebutuhan{UkuranTim: 1, KompleksitasDomain: 2}},
{"aplikasi internal perusahaan", Kebutuhan{UkuranTim: 4, KompleksitasDomain: 6}},
{"platform multi-tim", Kebutuhan{UkuranTim: 12, KompleksitasDomain: 8, DeployIndependen: true}},
}
for _, p := range profil {
fmt.Printf("%-30s -> %s\n", p.nama, Rekomendasi(p.keb))
}
}