Go Clean Arch: Kenapa Wajib di Prod
***

Daftar Isi

Go Clean Arch: Kenapa Wajib di Prod? (Gak Cuma Teori!)

Halo BeeGangs! Balik lagi kita bahas soal best practice di dunia Go. Kali ini topiknya agak berat tapi krusial banget buat masa depan aplikasi kamu: Clean Architecture.

Pernah gak kamu ngerasa kalau nambahin satu fitur kecil di aplikasi, tapi malah bikin error di mana-mana? Atau pas mau ganti database dari MySQL ke PostgreSQL, rasanya kayak mau pindah rumah saking ribetnya?

Kalau iya, kemungkinan besar kode kamu masih "spaghetti". Nah, di artikel ini kita bakal bedah kenapa Clean Arch itu bukan sekadar gaya-gayaan, tapi wajib hukumnya kalau aplikasi kamu mau masuk ke tahap production.

Masalah Klasik: "Yang Penting Jalan"

Gini deh, pas awal bikin projek, kita sering banget naruh semua logic mulai dari query database, validasi, sampai HTTP handler dalam satu file atau satu fungsi yang sama.

Di Go, hal ini gampang banget terjadi karena bahasanya simpel banget. Tapi problemnya muncul pas:

  • Fitur makin banyak, file
  • Mau bikin
  • Urutan logic bisnis ketuker sama urutan query.

Di sinilah Clean Architecture masuk buat ngerapiin "antrean" kode kamu.

Anatomi Clean Arch di Go

Idenya adalah Separation of Concerns. Kita bagi kode jadi beberapa layer yang punya tanggung jawab masing-masing. Di ekosistem Go, biasanya kita bagi jadi 4 layer utama:

  1. Domain / Entity: Isinya murni model data dan aturan bisnis inti. Layer ini gak boleh tahu apa-apa soal library luar (gak ada SQL, gak ada Echo atau Gin).
  2. Usecase / Business Logic: Tempat "otak" aplikasi kamu berada. Misalnya: "Kalau user daftar, kasih diskon 10% dan kirim email". Layer ini yang bertugas sebagai orchestrator flow dari aplikasi kamu.
  3. Repository: Khusus buat ngobrol sama data source (SQL, NoSQL, atau external API).
  4. Delivery / Handler: Layer paling luar. Mau lewat REST API, gRPC, atau CLI, semua urusannya di sini.

Kenapa Wajib di Production?

Testability (Bisa Tidur Nyenyak)

Dengan Clean Arch, kita pakai Interface buat nge-lem antar layer. Efeknya? Kamu bisa bikin Unit Test buat logic bisnis tanpa harus nyalain database beneran. Tinggal pakai mocking, tes jalan dalam hitungan detik.

Gak Ketergantungan sama Infra

Bayangkan hari ini kamu pakai MySQL, tapi bulan depan bos kamu minta pindah ke MongoDB karena alasan scaling. Kalau pakai Clean Arch, kamu cuma perlu ganti folder Repository. Logic bisnis di Usecase gak perlu disentuh sama sekali. Aman!

Tim Lebih Scalable

Kalau tim kamu nambah orang, mereka gak bakal bingung nyari kode. "Oh, kalau urusan database pasti di folder repository, kalau urusan alur bisnis pasti di usecase." Strukturnya sudah jelas dan baku.

Kapan Sebaiknya Gak Pakai?

Jujur aja, Clean Arch itu ada "biayanya": Boilerplate. Kamu bakal bikin banyak file dan interface di awal.

Jadi, kalau kamu cuma mau bikin:

  • Script buat pindahin data sekali pakai.
  • Aplikasi microservice super kecil yang cuma punya 1 atau 2 endpoint.
  • Prototipe atau MVP yang cuma buat demo besok pagi.

Maka pakai Clean Arch mungkin bakal kerasa overkill. Tapi kalau buat aplikasi jangka panjang, ini adalah investasi terbaik.

Gimana Implementasinya?

Biar gak bingung, kita ambil contoh simpel: fitur Get User. Gini cara kita bagi tugasnya:

Struktur Folder

Struktur folder di Go itu fleksibel, tapi biasanya kalau pakai Clean Arch, bentukannya bakal kayak gini:

1.
2├── internal
3│   ├── user            # Semua urusan user ngumpul di sini
4│   │   ├── handler.go  # Entry point (HTTP/gRPC)
5│   │   ├── usecase.go  # Business Logic
6│   │   ├── repo.go     # Query Database
7│   │   └── entity.go   # Model & Interface
8│   ├── product         # Module lain (misal: Product)
9│   │   ├── handler.go
10│   │   ├── usecase.go
11│   │   └── repo.go
12│   └── shared          # Library atau utilitas yang dipakai bareng
13├── cmd
14│   └── api
15│       └── main.go
16└── go.mod

Definisikan Interface di Layer Domain

Kuncinya ada di sini. Layer lain cuma boleh kenal sama Interface, bukan langsung ke struct-nya.

1// internal/user/entity.go
2package user
3
4type User struct {
5    ID   int
6    Name string
7}
8
9// Interface tetep ada biar gampang di-mock
10type Repository interface {
11    GetByID(id int) (*User, error)
12}

Layer Usecase (Logic Bisnis)

Di sini kita panggil interface tadi. Usecase gak peduli database-nya pakai apa, yang penting dia panggil fungsi GetByID.

1// internal/user/usecase.go
2package user
3
4type userUsecase struct {
5    repo Repository // Repository ini interface yang ada di entity.go
6}
7
8// Constructor buat inisialisasi usecase
9func NewUsecase(r Repository) userUsecase {
10    return userUsecase{
11        repo: r,
12    }
13}
14
15func (u userUsecase) GetDetail(id int) (*User, error) {
16    // Di sini kamu bisa selipin logic bisnis, 
17    // misal check cache dulu sebelum tembak repo
18    return u.repo.GetByID(id)
19}

Layer Repository (Realisasi Database)

Nah, di sini baru deh kamu tulis query SQL-nya. Kalau suatu saat mau ganti ke MongoDB, kamu cuma perlu bikin file baru yang memenuhi interface UserRepository.

1// internal/user/repo.go
2package user
3
4type mysqlRepo struct {
5    // db connection
6}
7
8func NewRepo() mysqlRepo {
9    return mysqlRepo{}
10}
11
12func (r mysqlRepo) GetByID(id int) (*User, error) {
13    return &User{ID: 1, Name: "BeeGangs User"}, nil
14}

Kenapa Repot Gini?

Mungkin kamu mikir, "Kok muter-muter ya kodenya?"

Gini alasannya: pas kamu mau bikin Unit Test buat userUsecase, kamu gak perlu koneksi database beneran. Kamu tinggal bikin "Repo Palsu" (Mock) yang asalnya dari interface tadi. Hasilnya, tes kamu bakal lari kenceng banget dan gak bakal flaky gara-gara urusan koneksi database.

Di production, fleksibilitas kayak gini yang bikin aplikasi kamu tahan banting pas ada perubahan teknologi atau tim yang makin gede.

Penutup

Jadi, BeeGangs, Clean Architecture itu soal disiplin. Emang agak ribet di awal karena harus ngetik lebih banyak, tapi percayalah, jauh lebih enak pusing di awal buat ngerapiin struktur daripada pusing tengah malam pas production meledak gara-gara kode yang berantakan.

***