Microservices vs Monolith: Kapan Harus Dipisah

Foto oleh Unsplash
Monolith adalah pilihan yang tepat untuk tim kecil di bawah 10 engineer, produk yang masih mencari product-market fit, atau sistem di mana modul-modulnya berbagi data dan cross-cutting concern yang signifikan. Tanpa tim DevOps atau platform khusus untuk mengelola infrastruktur layanan, monolith menghindari beban operasional yang ditimbulkan microservices sebelum Anda benar-benar membutuhkannya.
Strangler fig pattern secara bertahap mengekstrak layanan dari monolith yang sedang berjalan, merutekan traffic melalui API gateway ke monolith atau microservice baru berdasarkan path atau feature flag. Monolith menyusut seiring setiap layanan divalidasi di bawah load produksi sebelum kode monolith yang bersangkutan dihapus. Penulisan ulang besar-besaran hampir selalu gagal karena mengharuskan penggantian segalanya secara bersamaan sambil menjaga sistem tetap berjalan.
Distributed monolith adalah ketika aplikasi dipecah menjadi beberapa layanan tetapi tetap sangat terikat — mereka berbagi database, saling memanggil secara sinkron dalam rantai panjang, dan harus di-deploy bersama. Ini mengambil semua kompleksitas operasional microservices tanpa manfaatnya seperti scaling atau deployment independen. Sebelum mengekstrak sebuah layanan, ia harus benar-benar dapat memiliki data dan melakukan deploy secara independen.
Setiap layanan harus memiliki datanya secara eksklusif dan hanya mengeksposnya melalui API-nya sendiri — ini adalah pola database-per-service. Selama periode transisi, event sourcing atau change data capture (CDC) dapat menyinkronkan data antara monolith lama dan layanan baru. Dua layanan yang berbagi tabel database harus dihindari karena itu menciptakan kembali coupling ketat yang ingin dihindari oleh microservices.
Microservices membutuhkan service discovery, distributed tracing, centralized logging, health check, circuit breaker, dan API gateway sebelum siap untuk produksi. Alat distributed tracing seperti OpenTelemetry dengan Jaeger atau Tempo memberikan visibilitas end-to-end di seluruh layanan, sementara Prometheus dan Grafana mencakup metrik dan alerting per layanan. Service mesh seperti Istio atau Linkerd dapat menangani mTLS, retry, timeout, dan circuit breaking di level infrastruktur tanpa mengubah kode aplikasi.

Foto oleh Unsplash
Debat microservices vs monolith adalah salah satu keputusan arsitektur paling penting yang dibuat sebuah tim. Salah pilih dan Anda akan tenggelam dalam kompleksitas distributed system sebelum menemukan product-market fit, atau berjuang menskalakan monolith yang sudah menjadi bola kusut. Postingan ini memotong hype untuk memberi Anda kerangka dalam membuat pilihan yang tepat — dan strategi migrasi jika Anda perlu mengubah arah.
| Dimensi | Monolith | Microservices |
|---|---|---|
| Deployment | Satu unit deployable, satu pipeline build | Deploy independen per service, kecepatan rilis lebih tinggi tapi butuh lebih banyak koordinasi |
| Database | Satu database bersama, transaksi sederhana | Database per service, transaksi lintas-service butuh saga atau eventual consistency |
| Scaling | Scale seluruh aplikasi walau cuma satu modul yang sedang tinggi beban | Scale hanya service yang jadi bottleneck secara independen |
| Struktur tim | Cocok untuk tim kecil yang berbagi satu codebase | Sesuai Conway's Law, tim terpisah memiliki service masing-masing |
| Kompleksitas operasional | Satu log stream, satu proses untuk dipantau | Butuh distributed tracing, service mesh, dan logging terpusat |
| Isolasi kegagalan | Satu bug bisa membuat seluruh aplikasi crash | Service yang gagal bisa degradasi dengan baik jika sisanya tetap resilient |
| Paling cocok untuk | Produk tahap awal, tim kecil, batas domain belum jelas | Domain matang dengan bounded context jelas dan kebutuhan scaling independen |
Sebuah monolith adalah unit deployable tunggal di mana semua modul — pengguna, pesanan, inventori, penagihan — berbagi proses yang sama dan sering berbagi database yang sama. Arsitektur microservices mengurai modul-modul tersebut menjadi layanan yang dapat di-deploy secara independen yang berkomunikasi melalui jaringan. Perbedaannya terdengar sederhana, tetapi memiliki implikasi mendalam untuk tim, tooling, dan beban operasional Anda.
Monolith mendapat reputasi buruk, tetapi monolith yang terstruktur dengan baik dengan batas modul yang jelas adalah titik awal yang sangat baik untuk hampir semua produk. Anda memiliki satu codebase untuk dinavigasi, transaksi yang mencakup beberapa modul ditangani secara sepele oleh database, debugging mudah (satu proses, satu log stream), dan deployment adalah satu artifact. Beban operasional minimal dibandingkan microservices.
Microservices memungkinkan deployment dan scaling independen per layanan, isolasi kegagalan, dan heterogenitas teknologi — layanan yang berbeda dapat menggunakan bahasa atau database yang berbeda. Tetapi setiap batas layanan yang Anda tambahkan menambah latensi jaringan, kebutuhan distributed tracing, tantangan eventual consistency, dan kompleksitas koordinasi deployment di berbagai repository dan tim.
// Monolith: single shared database, direct function calls
// src/orders/orderService.ts
import { UserRepository } from "../users/userRepository";
import { InventoryRepository } from "../inventory/inventoryRepository";
export class OrderService {
async placeOrder(userId: string, items: OrderItem[]): Promise<Order> {
const user = await UserRepository.findById(userId);
await InventoryRepository.reserveItems(items);
return OrderRepository.create({ userId, items, status: "pending" });
}
}
// Microservices: HTTP/message calls across service boundaries
// order-service/src/orderService.ts
export class OrderService {
async placeOrder(userId: string, items: OrderItem[]): Promise<Order> {
// Call user-service via HTTP
const user = await fetch(`http://user-service/users/${userId}`).then(r => r.json());
// Call inventory-service via message queue
await messageBroker.publish("inventory.reserve", { items, orderId: newId() });
return OrderRepository.create({ userId, items, status: "pending" });
}
}Buku 'Monolith to Microservices' dari Sam Newman merekomendasikan memulai dengan modular monolith — batas modul internal yang kuat yang diterapkan oleh kode, bukan network call. Ini memberi Anda pemisahan kepentingan tanpa overhead jaringan, dan membuat migrasi di masa depan jauh lebih mudah.
Pilihan yang tepat bergantung pada ukuran tim, frekuensi deployment, kebutuhan scaling, dan struktur organisasi Anda. Hukum Conway itu nyata: arsitektur sistem Anda akan mencerminkan struktur komunikasi tim Anda. Tim kecil yang membangun satu produk hampir selalu mendapat manfaat dari monolith. Organisasi besar dengan beberapa tim, SLA yang berbeda per layanan, dan kemampuan untuk memiliki pipeline deployment independen adalah rumah alami dari microservices.
Anda memiliki tim kecil (kurang dari 10 engineer), Anda masih mencari product-market fit dan perlu iterasi cepat, modul Anda berbagi data dan cross-cutting concern yang signifikan, Anda tidak memiliki tim DevOps atau platform khusus untuk mengelola infrastruktur layanan, atau Anda membangun alat internal atau MVP di mana kesederhanaan adalah yang utama.
Tim Anda berukuran dan terstruktur secara independen berdasarkan layanan, Anda perlu menskalakan komponen tertentu secara independen, layanan yang berbeda memiliki kebutuhan keandalan atau kepatuhan yang berbeda, Anda perlu mendukung beberapa lingkungan deployment secara independen, atau bagian sistem yang berbeda dimiliki oleh tim yang tidak boleh berbagi siklus deployment.
Jika Anda beralih dari monolith ke microservices, strangler fig pattern adalah pendekatan paling aman. Daripada penulisan ulang besar-besaran (yang hampir selalu gagal), Anda secara bertahap mengekstrak layanan dari monolith sambil tetap menjalankannya. API gateway merutekan request ke monolith atau microservice baru berdasarkan path atau feature flag. Seiring waktu, monolith menyusut saat layanan diekstrak.
Mulai dengan mengidentifikasi bounded context yang paling worth diekstrak — idealnya yang memiliki batas API yang jelas, kebutuhan scaling independen, dan tidak ada data coupling yang erat dengan monolith lainnya. Ekstrak data terlebih dahulu: beri layanan baru database-nya sendiri. Kemudian rutekan traffic melalui API gateway. Validasi layanan baru di bawah load produksi sebelum menghapus kode monolith.
Anti-pattern paling berbahaya adalah distributed monolith: Anda telah memecah aplikasi menjadi beberapa layanan, tetapi mereka sangat terikat — mereka berbagi database, saling memanggil secara sinkron dalam rantai panjang, dan harus di-deploy bersama. Anda mengambil semua kompleksitas operasional dari microservices tanpa manfaatnya. Sebelum mengekstrak sebuah layanan, pastikan ia benar-benar dapat memiliki data dan melakukan deploy secara independen.
Bagian tersulit dari migrasi microservices adalah data. Gunakan pola database-per-service: setiap layanan memiliki datanya sendiri dan hanya mengeksposnya melalui API-nya. Gunakan event sourcing atau change data capture (CDC) untuk menyinkronkan data antara monolith lama dan layanan baru selama periode transisi. Jangan pernah membiarkan dua layanan berbagi tabel database — itu menciptakan kembali coupling ketat yang ingin Anda hindari.
# Strangler Fig pattern: route by feature flag
# api-gateway/nginx.conf
location /api/users {
# New microservice handles /users
proxy_pass http://user-service:3001;
}
location /api/orders {
# Still monolith for now
proxy_pass http://monolith:3000;
}
location /api/inventory {
# Migrated to microservice
proxy_pass http://inventory-service:3002;
}Microservices mengalihkan kompleksitas dari kode Anda ke infrastruktur Anda. Anda membutuhkan service discovery, distributed tracing, centralized logging, health check, circuit breaker, dan API gateway sebelum arsitektur microservices Anda siap produksi. Anggarkanlah waktu untuk pekerjaan infrastruktur ini — ini tidak opsional.
Dalam monolith, Anda memiliki satu log stream. Dalam microservices, satu request pengguna dapat menyentuh sepuluh layanan. Distributed tracing (OpenTelemetry + Jaeger atau Tempo) memberi Anda visibilitas end-to-end. Structured logging dengan correlation ID memungkinkan Anda memfilter log di seluruh layanan untuk satu request. Metrik dan alerting per layanan (Prometheus + Grafana) memberi tahu Anda layanan mana yang menjadi bottleneck.
Service mesh seperti Istio atau Linkerd menangani mTLS antar layanan, load balancing, retry, timeout, dan circuit breaking di level infrastruktur — tanpa mengubah kode aplikasi. API gateway (Kong, AWS API Gateway) menangani autentikasi, rate limiting, dan routing di edge. Keduanya adalah force multiplier untuk arsitektur microservices tetapi menambah kompleksitas operasional tersendiri.
Gunakan health check dan readiness probe pada setiap layanan sejak hari pertama. Layanan yang gagal readiness probe-nya secara otomatis dihapus dari load balancer, mencegah cascading failure sebelum mencapai pengguna.
Keputusan monolith vs microservices tidak permanen, dan Anda tidak boleh memperlakukannya demikian. Mulai sederhana. Bangun monolith yang terstruktur dengan baik. Ketika bottleneck tertentu atau masalah kepemilikan tim benar-benar membutuhkan ekstraksi layanan, lakukan secara bedah, mengikuti strangler fig pattern. Hindari microservices-by-default dan dekomposisi prematur — keduanya adalah sumber kompleksitas yang tidak perlu yang paling umum dalam tim software modern.
Konsep arsitektur kunci yang dibahas di sini meliputi monolith, microservices, strangler fig, service mesh, and bounded context.