Mengadopsi microservices terlalu dini membebani sistem dengan operational overhead yang tidak perlu: latensi serialisasi jaringan, orkestrasi cluster kompleks, dan konsumsi memori berlebih. Arsitektur Modular Monolith di Go Fiber menawarkan alternatif realistis: memisahkan domain secara logis di level kode tanpa membayar pajak infrastruktur komunikasi antar-layanan.
Efisiensi Biaya dan Komputasi Operasional
Go memiliki runtime yang sangat efisien, namun mendistribusikan sistem ke dalam puluhan microservices menimbulkan redundansi resource yang substansial:
- Overhead Memori Runtime: Setiap microservice Go menjalankan instance runtime sendiri, termasuk scheduler goroutine, heap metadata, dan background garbage collector (GC). Sepuluh microservices minimal memakan baseline Resident Set Size (RSS) sepuluh kali lipat dibanding satu binary monolitik yang menangani seluruh domain.
- Eliminasi Latensi I/O Jaringan: Komunikasi inter-service melalui gRPC atau REST/HTTP membutuhkan serialisasi (JSON/Protobuf marshaling), alokasi buffer jaringan, soket TCP, enkripsi TLS, dan kontekstual deadline. Modular monolith mengganti overhead ini dengan direct pointer passing di memori dengan alokasi heap mendekati nol.
- Throughput Engine Fasthttp: Go Fiber dibangun di atas
fasthttpyang menerapkan teknik zero memory allocation pada hot-path routing. Menggabungkan efisiensi Fiber dengan direct in-memory call menghasilkan respons API p99 jauh lebih konsisten dibanding agregasi API gateway ke downstream microservices.
Analisis Trade-Off Teknis
1. Direct Memory Call vs gRPC/HTTP Inter-Service
Pada microservices, kegagalan network call mengharuskan implementasi retries, backoff, circuit breaker, dan distributed tracing via OpenTelemetry. Pada modular monolith, pemanggilan antar-modul cukup berupa eksekusi interface Go reguler:
// Direct call: instan, aman secara tipe data pada compile time, zero serialization cost
isAvailable, err := m.inventoryModule.CheckStock(ctx, productID, qty)
Risikonya: tight coupling jika modularitas dilanggar via mutable shared state. Pointer yang dimodifikasi oleh modul penerima dapat memicu race condition jika dieksekusi secara asinkron tanpa proteksi mutex.
2. Shared Connection Pool vs Database-per-Service
Microservices mewajibkan pola database-per-service untuk isolasi data. Modular monolith umumnya mengandalkan satu database cluster bersama (shared database):
- Keuntungan: Efisiensi connection pool. Daripada 20 microservices membuka masing-masing 10-20 pool koneksi ke PostgreSQL (menghabiskan batas
max_connectionsdatabase), satu binary Go Fiber hanya memerlukan satu pool terkelola via*sql.DBataupgxpool.Pool. Transaksi ACID antar-tabel dapat dieksekusi dalam satu database transaction lokal tanpa koordinasi rumit seperti Saga Pattern. - Kelemahan: Risiko bypass isolasi domain. Engineer dapat dengan mudah menulis query SQL yang melakukan
JOINlangsung ke tabel milik modul lain, merusak boundary domain secara permanen. Mitigasinya adalah isolasi schema PostgreSQL (misal schemaorderdan schemainventory) dengan database user terpisah jika diperlukan.
Implementasi Struktur Direktori Menggunakan Go Internal Package
Kompiler Go membatasi impor package di dalam direktori internal/ hanya untuk package induk langsungnya. Aturan ini memastikan modul lain tidak dapat mengimpor package private di dalam sub-modul.
my-fiber-app/
├── cmd/
│ └── api/
│ └── main.go
├── internal/
│ ├── inventory/
│ │ ├── handler.go
│ │ ├── repository.go
│ │ ├── service.go
│ │ └── module.go // Public contract & interface untuk modul lain
│ └── order/
│ ├── handler.go
│ ├── repository.go
│ └── service.go
├── go.mod
└── go.sum
Definisi Contract Antar-Modul
Gunakan prinsip consumer-driven interface. Modul order mendefinisikan interface dependensinya sendiri, bukan mengimpor struct konkret dari inventory.
// internal/order/service.go
package order
import "context"
type StockChecker interface {
CheckStock(ctx context.Context, productID string, quantity int) (bool, error)
}
type Service struct {
stockChecker StockChecker
}
func NewService(sc StockChecker) *Service {
return &Service{stockChecker: sc}
}
func (s *Service) CreateOrder(ctx context.Context, productID string, qty int) error {
available, err := s.stockChecker.CheckStock(ctx, productID, qty)
if err != nil || !available {
return ErrStockUnavailable
}
// Proses order logic...
return nil
}
Registrasi Route Fiber per Bounded Context
Setiap modul mengekspos fungsi untuk mendaftarkan endpoint HTTP-nya ke sub-router Fiber (fiber.Router):
// internal/order/handler.go
package order
import "github.com/gofiber/fiber/v2"
type Handler struct {
service *Service
}
func NewHandler(s *Service) *Handler {
return &Handler{service: s}
}
func (h *Handler) MountRoutes(router fiber.Router) {
group := router.Group("/orders")
group.Post("/", h.handleCreate)
}
func (h *Handler) handleCreate(c *fiber.Ctx) error {
// Parsing & delegasi ke service
return c.SendStatus(fiber.StatusCreated)
}
Wiring Dependency di Entry Point
Inisialisasi dilakukan terpusat pada cmd/api/main.go:
// cmd/api/main.go
package main
import (
"log"
"github.com/gofiber/fiber/v2"
"my-fiber-app/internal/inventory"
"my-fiber-app/internal/order"
)
func main() {
app := fiber.New()
api := app.Group("/api/v1")
// Inisialisasi Modul Inventory
inventoryService := inventory.NewService()
inventoryHandler := inventory.NewHandler(inventoryService)
inventoryHandler.MountRoutes(api)
// Inisialisasi Modul Order (Inject inventoryService sebagai dependency)
orderService := order.NewService(inventoryService)
orderHandler := order.NewHandler(orderService)
orderHandler.MountRoutes(api)
log.Fatal(app.Listen(":3000"))
}
Batas Skala: Kapan Modul Harus Dipecah?
Pertahankan arsitektur Modular Monolith selama arsitektur ini mencukupi. Pertimbangkan ekstraksi modul menjadi independent microservice hanya saat kondisi berikut terpenuhi:
- Beban Komputasi Asimetris: Salah satu modul (misalnya modul pemrosesan video, image rendering, atau training ML kecil) membutuhkan resource CPU/GPU ekstrem yang memicu horizontal scaling terus-menerus. Memisahkan modul ini mencegah seluruh monolit ikut ter-scale tanpa kebutuhan riil.
- Skala Organisasi Tim: Skala tim developer melampaui 30-50 orang dan frekuensi deployment branch sering menghasilkan bottleneck merge conflict atau queue build CI/CD yang menghambat rilis harian.
- SLA dan Blast Radius Kritis: Kerusakan fatal (seperti runtime panic atau fatal memory leak) pada modul pelengkap tidak boleh menjatuhkan domain inti (core transactional engine).
- Kebutuhan Regulasi Data: Domain modul tertentu wajib mematuhi standar keamanan spesifik (seperti PCI-DSS atau HIPAA) yang memerlukan audit infrastruktur terisolasi secara ketat.
Komentar
0 komentar
Masuk ke akun kamu untuk ikut berkomentar.
Belum ada komentar
Jadilah yang pertama ikut berdiskusi!