Monolith vs Microservices: Kapan Harus Split?
Pertanyaan yang paling sering saya dapat dari developer Indonesia: 'Kapan harus pindah dari monolith ke microservices?' Jawaban saya selalu: jangan terburu-buru. Dari pengalaman saya bekerja di beberapa startup di Bandung, monolith yang well-structured seringkali lebih baik daripada microservices yang poorly designed.
Microservices menjadi worth it ketika: tim sudah cukup besar (10+ developer), ada bagian sistem yang perlu di-scale secara independen, atau ada requirement untuk technology diversity di berbagai service.
Designing Service Boundaries
Hal paling kritis dalam microservices adalah menarik batas antar service dengan benar. Saya menggunakan Domain-Driven Design (DDD) sebagai panduan:
// Service boundaries berdasarkan domain
// Order Service - menangani semua terkait pesanan
type OrderService struct {
repository OrderRepository
eventBus EventBus
client *http.Client
}
func (s *OrderService) CreateOrder(ctx context.Context, req CreateOrderRequest) (*Order, error) {
// Validasi stok via Inventory Service
stock, err := s.client.CheckStock(ctx, req.ProductID, req.Quantity)
if err != nil {
return nil, fmt.Errorf("failed to check stock: %w", err)
}
// Buat order
order := &Order{
ID: uuid.New(),
ProductID: req.ProductID,
Quantity: req.Quantity,
Status: OrderStatusPending,
CreatedAt: time.Now(),
}
if err := s.repository.Save(ctx, order); err != nil {
return nil, err
}
// Publish event untuk service lain
s.eventBus.Publish(OrderCreatedEvent{
OrderID: order.ID,
Amount: stock.Price * float64(req.Quantity),
})
return order, nil
}Inter-Service Communication
Ada dua pola komunikasi utama: synchronous (REST/gRPC) dan asynchronous (message queue). Saya menyarankan menggunakan synchronous untuk queries dan asynchronous untuk commands:
// Synchronous: REST untuk query
func (c *ProductClient) GetProduct(ctx context.Context, id string) (*Product, error) {
resp, err := c.client.Get(fmt.Sprintf("%s/products/%s", c.baseURL, id))
if err != nil {
return nil, err
}
defer resp.Body.Close()
var product Product
json.NewDecoder(resp.Body).Decode(&product)
return &product, nil
}
// Asynchronous: Event untuk commands
type OrderCreatedEvent struct {
OrderID string `json:"order_id"`
Amount float64 `json:"amount"`
}
// Inventory Service listens
eventBus.Subscribe("order.created", func(event OrderCreatedEvent) error {
return inventoryService.ReserveStock(event.OrderID)
})Deployment Strategy
Untuk deployment, saya menggunakan Kubernetes dengan rolling update strategy. Setiap service di-deploy secara independen dan bisa di-scale sesuai kebutuhan. Docker Compose untuk development environment, Kubernetes untuk staging dan production.
Yang paling penting: implement proper circuit breaker dan retry mechanism untuk inter-service calls. Di distributed system, network failure adalah hal yang pasti terjadi, bukan kemungkinan.
Monitoring dan Debugging
Monitoring di microservices lebih challenging karena ada banyak service yang saling berkomunikasi. Saya implement distributed tracing dengan Jaeger dan centralized logging dengan ELK stack. Dengan tracing, kita bisa melihat bagaimana request mengalir dari satu service ke service lain, dan dengan cepat menemukan bottleneck atau error.