Episode ini membangun fondasi kode yang terorganisir: pemisahan handler, service, dan repository, layout internal sesuai konvensi Go, dependency injection sederhana tanpa library, serta modularisasi setup router dan middleware agar mudah diuji dan diskalakan.

Handler yang menumpuk semua logika dalam satu file memang cepat ditulis, tapi menjadi kutukan saat aplikasi tumbuh. Clean architecture memisahkan concern sehingga kode bisa diuji, dibaca, dan diubah tanpa rasa takut. Inilah episode yang mengubah project kecil kalian menjadi codebase yang bisa bertahan.
Episode 8 ini membangun fondasi kode terorganisir: pemisahan handler, service, dan repository; layout internal/ sesuai konvensi Go; dependency injection sederhana tanpa library; serta modularisasi setup router dan middleware.
Prinsip dasarnya sederhana: setiap lapisan punya satu tanggung jawab, dan arah dependensi hanya satu arah.
internal/
handler/
user_handler.go
service/
user_service.go
repository/
user_repository.go
model/
user.goDirektori internal/ adalah fitur bahasa Go: package di dalamnya tidak bisa diimport dari luar module kalian. Ini pengaman kompilasi yang menjaga lapisan internal tetap privat.
mkdir -p internal/handler internal/service internal/repository internal/modelKonvensi lain yang penting: nama file menunjuk package yang sama (user_handler.go berisi package handler), dan setiap lapisan hanya boleh bergantung pada lapisan di bawahnya — handler boleh memanggil service, service boleh memanggil repository, tapi tidak sebaliknya.
Go tidak memerlukan framework DI. Cukup definisikan antarmuka dan suntikkan implementasinya lewat constructor:
type UserRepository interface {
FindByID(ctx context.Context, id int) (*model.User, error)
}
type UserService struct {
repo UserRepository
}
func NewUserService(repo UserRepository) *UserService {
return &UserService{repo: repo}
}
type UserHandler struct {
svc *UserService
}
func NewUserHandler(svc *UserService) *UserHandler {
return &UserHandler{svc: svc}
}Dengan pola NewUserService(repo) dan NewUserHandler(svc), kalian menyuntikkan dependency dari atas ke bawah. Untuk pengujian, cukup berikan implementasi mock dari antarmuka.
Perakitan dilakukan di main — satu tempat yang jelas untuk melihat seluruh dependency aplikasi:
func main() {
e := echo.New()
repo := repository.NewUserRepository()
svc := service.NewUserService(repo)
handler := handler.NewUserHandler(svc)
handler.RegisterRoutes(e.Group("/api/v1"))
e.Logger.Fatal(e.Start(":8080"))
}Method RegisterRoutes menerima group dan mendaftarkan seluruh route handler. Dengan ini, main tetap ramping dan tiap bagian bisa diuji terpisah.
Setup middleware sebaiknya dikelompokkan agar tidak bercampur dengan deklarasi route. Buat fungsi tersendiri:
func setupMiddleware(e *echo.Echo) {
e.Use(middleware.RequestLoggerWithConfig(loggingConfig()))
e.Use(middleware.Recover())
e.Use(middleware.CORS())
}Pemisahan ini membuat kebijakan middleware bisa diubah di satu tempat dan diuji sebagai satu kesatuan.
Setiap domain punya file route sendiri, terdaftar dari RegisterRoutes. Ini yang disebut modular router setup:
func (h *UserHandler) RegisterRoutes(g *echo.Group) {
g.GET("/users", h.List)
g.GET("/users/:id", h.Get)
g.POST("/users", h.Create)
}Saat aplikasi berkembang, tambahkan modul baru tanpa menyentuh file lain — cukup panggil RegisterRoutes dari main.
Arsitektur ini mengubah cara kalian menguji. Handler bisa diuji dengan httptest (episode 17), service diuji murni tanpa HTTP, dan repository diuji dengan database pengujian. Setiap lapisan memiliki scope pengujian yang jelas.
go vet ./...Jalankan go vet ./... dari root project untuk memastikan seluruh package di bawah internal/ tersusun benar.
Episode 8 membangun fondasi kode terorganisir: handler, service, dan repository sebagai tiga lapisan dengan satu arah dependensi; layout internal/ sebagai pengaman kompilasi; dependency injection sederhana lewat constructor; serta modularisasi setup middleware dan route per modul.
Inti yang harus dibawa pulang:
internal/ mencegah package diimport dari luar module.main menjadi satu-satunya tempat perakitan dependency.RegisterRoutes.Di episode 9 selanjutnya kita akan membahas integrasi database & ORM — koneksi PostgreSQL dan MySQL dengan pgx, database/sql, GORM, dan sqlc, connection pooling, migrasi skema, serta implementasi CRUD lengkap dalam handler Echo.