Dalam dunia pengembangan backend modern, menjaga kode agar tetap bersih, mudah diuji, dan independen dari framework atau database adalah tantangan utama. Di sinilah Hexagonal Architecture (atau Ports and Adapters) berperan.
Artikel ini akan membedah bagaimana mengimplementasikan arsitektur ini menggunakan bahasa pemrograman Go dengan bantuan framework GoFr, berdasarkan studi kasus sistem blog dari repositori Halovina.
Sebelum masuk ke kode, mari kita pahami konsep dasarnya. Hexagonal Architecture membagi aplikasi Anda menjadi tiga lapisan utama:
Core (Domain): Jantung aplikasi Anda. Berisi logika bisnis murni dan entitas (misal: struct Article atau User). Lapisan ini tidak boleh tahu tentang database, HTTP, atau API eksternal.
Ports: Antarmuka (interface) yang mendefinisikan bagaimana dunia luar berkomunikasi dengan Core (Primary Port) dan bagaimana Core berkomunikasi dengan dunia luar (Secondary Port).
Adapters: Implementasi konkret dari Ports. Contohnya: Handler HTTP, Repositori SQL, atau klien API pihak ketiga.
Proyek golang-gofr-example-blog-system adalah contoh sempurna bagaimana teori ini diterjemahkan menjadi kode nyata. Proyek ini menggunakan GoFr, sebuah framework Go yang "opiniated" dan kaya fitur (observability, database management, dll) untuk mempercepat pengembangan.
Implementasi Hexagonal Architecture biasanya tercermin dari struktur direktori:
├── cmd
│ └── main.go # Entry point aplikasi
├── internal
│ ├── core
│ │ ├── domain # Entitas bisnis (e.g., Post)
│ │ ├── services # Logika bisnis (e.g., CreatePost)
│ │ └── ports # Interface (Input & Output)
│ ├── repositories # Adapter ke Database (Secondary Adapter)
│ └── handlers # Adapter ke HTTP/Web (Primary Adapter)
└── configs # Konfigurasi envDi folder internal/core, kita mendefinisikan aturan mainnya.
Domain (domain/models.go): Struct sederhana untuk menjaga kemurnian data.
type Post struct {
ID uint `gorm:"primaryKey" json:"id"`
Title string `gorm:"not null" json:"title"`
Content string `gorm:"type:text" json:"content"`
...
}
Ports (ports/ports.go): Kita menggunakan interface untuk memisahkan logika.
// Output Port: Database Repository type BlogRepository interface { CreateUser(user *domain.User) error ... } // Input Port: Service Logic type BlogService interface { GetAllPosts() ([]domain.Post, error) ... }Di sinilah GoFr sangat membantu. Framework ini menyederhanakan pembuatan adapter database dan HTTP handler.
Repository Adapter (repositories/gorm_repo.go): Implementasi PostRepository menggunakan koneksi SQL yang disediakan Gorm.
type GormRepository struct {
db *gorm.DB
}
func NewGormRepository(db *gorm.DB) ports.BlogRepository {
return &GormRepository{db: db}
}
func (r *GormRepository) CreateUser(user *domain.User) error {
return r.db.Create(user).Error
}
....
HTTP Handler Adapter (handlers/http/post_handler.go): Handler ini menerima request HTTP, memanggil Service, dan mengembalikan respons JSON standar.
type HTTPHandler struct {
svc ports.BlogService
}
func NewHTTPHandler(svc ports.BlogService) *HTTPHandler {
return &HTTPHandler{svc: svc}
}
func (h *HTTPHandler) CreatePost(c *gofr.Context) (interface{}, error) {
....
}
main.goBagian main.go berfungsi sebagai "lem" yang menyatukan dependensi (Dependency Injection).
func main() {
// 1. Init GoFr
app := gofr.New()
// 2. Database Setup (GORM)
dsn := fmt.Sprintf("host=%s user=%s password=%s dbname=%s port=5432 sslmode=disable",
os.Getenv("DB_HOST"), os.Getenv("DB_USER"), os.Getenv("DB_PASSWORD"), os.Getenv("DB_NAME"))
db, err := gorm.Open(postgres.Open(dsn), &gorm.Config{})
if err != nil {
app.Logger().Errorf("Failed to connect to DB: %v", err)
}
// Auto Migrate
db.AutoMigrate(&domain.User{}, &domain.Post{}, &domain.Comment{})
// 3. Dependency Injection (Hexagonal Architecture Wiring)
repo := repository.NewGormRepository(db)
secret := "very-secret" // Should ideally be from env
svc := services.NewService(repo, secret)
h := handlers.NewHTTPHandler(svc)
// 4. Routes
app.POST("/register", h.Register)
app.POST("/login", h.Login)
app.GET("/posts", h.GetAllPosts)
app.POST("/posts", h.CreatePost)
app.DELETE("/posts/{id}", h.DeletePost)
// 5. Swagger Configuration (GoFr will automatically serve openapi.json
// placed in the `static` folder at the path `/.well-known/swagger`)
// Ensure the `static/openapi.json` file exists in your project root.
// Start App
app.Run()
}
Framework Agnostic (Sebagian): Meskipun kita menggunakan GoFr untuk routing dan database connection, logika bisnis di core sama sekali tidak tahu menahu tentang GoFr. Jika ingin ganti framework, Anda hanya perlu mengubah layer adapter.
Mudah di-Test: Karena semua dependensi menggunakan interface (Ports), Anda bisa dengan mudah membuat mock untuk repository saat melakukan Unit Test pada Service.
Observability Bawaan: Dengan GoFr, endpoint yang Anda buat otomatis memiliki fitur tracing, metrics, dan logging tanpa perlu konfigurasi tambahan yang rumit.
Menggabungkan kekuatan Go, GoFr, dan Hexagonal Architecture menghasilkan aplikasi yang tidak hanya cepat secara performa, tetapi juga cepat dalam pengembangan (development speed) dan mudah dirawat.
Repositori halovina/golang-gofr-example-blog-system memberikan cetak biru yang solid bagi Anda yang ingin memulai perjalanan microservice dengan standar industri.
Untuk tutorial visual langkah demi langkah mengenai konfigurasi awal framework ini, simak video berikut:
Setup Golang Microservices in a Flash: GoFr Framework Configuration Tutorial
Video ini sangat relevan karena membahas dasar konfigurasi GoFr yang digunakan dalam arsitektur proyek di atas, membantu Anda memahami cara menyiapkan environment sebelum menulis kode logika bisnisnya.