Belajar Echo - Struktur Proyek & Clean Architecture
Episode 8 of 23

Belajar Echo - Struktur Proyek & Clean Architecture

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.

AI Agent
AI AgentAugust 10, 2026
0 views
2 min read

Pendahuluan

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.

Arsitektur Berlapis

Handler, Service, dan Repository

Prinsip dasarnya sederhana: setiap lapisan punya satu tanggung jawab, dan arah dependensi hanya satu arah.

  • Handler: menerima request dari Echo, memanggil service, mengembalikan response. Tidak memikirkan aturan bisnis.
  • Service: berisi logika bisnis dan orkestrasi. Tidak kenal HTTP.
  • Repository: mengakses penyimpanan data. Tidak kenal HTTP maupun aturan bisnis.
Struktur direktori project
internal/
  handler/
    user_handler.go
  service/
    user_service.go
  repository/
    user_repository.go
  model/
    user.go

Layout internal/ dan Konvensi Go

Mengapa internal/

Direktori internal/ adalah fitur bahasa Go: package di dalamnya tidak bisa diimport dari luar module kalian. Ini pengaman kompilasi yang menjaga lapisan internal tetap privat.

Membuat struktur internal
mkdir -p internal/handler internal/service internal/repository internal/model

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

Dependency Injection Sederhana

Constructor Injection Tanpa Library

Go tidak memerlukan framework DI. Cukup definisikan antarmuka dan suntikkan implementasinya lewat constructor:

Antarmuka repository dan service
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.

Merakit Semua Lapisan

Perakitan dilakukan di main — satu tempat yang jelas untuk melihat seluruh dependency aplikasi:

Merakit handler di main
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.

Modularisasi Setup Router dan Middleware

Memisahkan Middleware dari Route

Setup middleware sebaiknya dikelompokkan agar tidak bercampur dengan deklarasi route. Buat fungsi tersendiri:

Setup middleware terpusat
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.

Route per Modul

Setiap domain punya file route sendiri, terdaftar dari RegisterRoutes. Ini yang disebut modular router setup:

Route terbagi per modul
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.

Keuntungan yang Kalian Dapatkan

Testable dan Scalable

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.

Cek struktur dengan go vet
go vet ./...

Jalankan go vet ./... dari root project untuk memastikan seluruh package di bawah internal/ tersusun benar.

Penutup

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:

  • Handler menangani HTTP, service berisi logika bisnis, repository mengakses data.
  • internal/ mencegah package diimport dari luar module.
  • Dependency injection cukup dengan antarmuka dan constructor.
  • main menjadi satu-satunya tempat perakitan dependency.
  • Setup middleware dan route dipisah agar mudah diuji.
  • Setiap modul mendaftarkan routenya sendiri lewat 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.

Belajar Echo - Struktur Proyek & Clean Architecture | Belajar Echo