Arsitektur Microservices: Kapan dan Bagaimana
Panduan pragmatis tentang arsitektur microservices, mencakup kapan masuk akal, bagaimana mendesain batasan service, dan bagaimana menghindari jebakan umum.
Pertanyaan Microservices
Setiap tim engineering yang berkembang pada akhirnya bertanya: haruskah kita beralih ke microservices? Jawaban jujurnya lebih bernuansa dari yang kebanyakan artikel sarankan. Microservices menyelesaikan masalah nyata, tapi memperkenalkan kompleksitas nyata. Arsitektur yang tepat bergantung pada ukuran tim, domain, dan kematangan operasional Anda.
Panduan ini membantu Anda memutuskan apakah microservices sesuai untuk situasi Anda, dan jika ya, bagaimana mengimplementasikannya dengan baik.
Kapan Microservices Masuk Akal
Alasan Baik untuk Mengadopsi
Deployment independen: Tim berbeda perlu men-deploy komponen mereka pada jadwal berbeda tanpa berkoordinasi dengan setiap tim lainnya.
Keberagaman teknologi: Beberapa bagian sistem Anda mendapat manfaat dari pilihan teknologi berbeda. Pipeline machine learning di Python, API real-time di Go, dan aplikasi web di Node.js masing-masing bisa menggunakan alat terbaik untuk tugasnya.
Scaling independen: Service pemrosesan pesanan Anda butuh 20 instance selama flash sale, tapi service katalog produk hanya butuh 2. Microservices memungkinkan Anda menskalakan setiap komponen secara independen.
Otonomi tim: Tim 5 hingga 8 orang memiliki seluruh service end-to-end: development, testing, deployment, dan monitoring. Model kepemilikan ini mengurangi overhead koordinasi.
Isolasi kegagalan: Saat mesin rekomendasi crash, seharusnya tidak menjatuhkan alur checkout.
Kapan Tetap Monolitik
Tim kecil: Jika seluruh tim engineering Anda di bawah 10 orang, monolit yang terstruktur baik hampir selalu pilihan yang lebih baik. Overhead koordinasi microservices melebihi manfaatnya.
Produk tahap awal: Saat Anda masih menemukan apa yang seharusnya dilakukan produk, microservices mengunci Anda ke batasan service terlalu dini. Bangun monolit dulu, lalu ekstrak service saat batasan stabil muncul.
Kemampuan operasional terbatas: Microservices membutuhkan monitoring canggih, logging, pipeline deployment, dan incident response. Jika Anda belum memilikinya, microservices akan menciptakan kekacauan.
Mendesain Batasan Service
Domain-Driven Design
Metode paling andal untuk mengidentifikasi batasan service berasal dari Domain-Driven Design (DDD). Setiap service selaras dengan bounded context: bagian dari domain bisnis dengan aturan konsistensi internalnya sendiri.
Untuk platform e-commerce:
Bounded Context:
├── Catalog Service
│ Memiliki: produk, kategori, harga
│ Data: deskripsi produk, gambar, harga
│
├── Order Service
│ Memiliki: pesanan, baris pesanan, status pesanan
│ Data: detail pesanan, status pemenuhan
│
├── Inventory Service
│ Memiliki: level stok, lokasi gudang
│ Data: kuantitas, reservasi, pergerakan
│
├── Customer Service
│ Memiliki: profil pelanggan, alamat
│ Data: info kontak, preferensi
│
├── Payment Service
│ Memiliki: pemrosesan pembayaran, refund
│ Data: metode pembayaran, catatan transaksi
│
└── Notification Service
Memiliki: email, SMS, push notification
Data: template, status pengiriman
Tes Batasan
Batasan service yang baik melewati pengecekan ini:
- Service bisa dikembangkan oleh satu tim (5 hingga 8 orang)
- Perubahan dalam service jarang membutuhkan perubahan di service lain
- Service memiliki data store sendiri yang tidak bisa diakses langsung oleh service lain
- API service stabil dan tidak sering berubah
- Service bisa di-deploy secara independen tanpa koordinasi
Jika perubahan sering membutuhkan pembaruan beberapa service secara bersamaan, service tersebut mungkin terlalu tightly coupled dan harus digabung atau batasannya digambar ulang.
Pola Komunikasi
Sinkron: REST dan gRPC
Untuk interaksi request-response di mana pemanggil membutuhkan jawaban segera:
// REST: Sederhana, mudah dibaca manusia, didukung luas
GET /api/products/12345
Response: { "id": "12345", "name": "Widget", "price": 25000 }
// gRPC: Protokol biner, lebih cepat, strongly typed
service ProductService {
rpc GetProduct(ProductRequest) returns (Product);
rpc ListProducts(ListRequest) returns (stream Product);
}
Gunakan REST untuk API publik dan gRPC untuk komunikasi service-to-service internal di mana performa penting.
Asinkron: Event-Driven
Untuk interaksi di mana pemanggil tidak membutuhkan respons segera:
// Order Service mempublikasikan event
{
"event": "order.created",
"timestamp": "2026-01-15T10:30:00Z",
"data": {
"orderId": "ORD-2026-001",
"customerId": "CUST-456",
"items": [
{ "productId": "PROD-789", "quantity": 2 }
],
"totalAmount": 500000
}
}
// Inventory Service subscribe dan mereservasi stok
// Payment Service subscribe dan menginisiasi pembayaran
// Notification Service subscribe dan mengirim email konfirmasi
Arsitektur event-driven memisahkan service secara temporal. Order Service tidak menunggu inventori, pembayaran, atau notifikasi selesai. Setiap service memproses event dengan kecepatannya sendiri.
Memilih Pola yang Tepat
| Skenario | Pola | Alasan |
|---|---|---|
| User meminta detail produk | REST sinkron | Respons segera dibutuhkan |
| Service perlu memvalidasi inventori | gRPC sinkron | Panggilan internal cepat |
| Pesanan dibuat, perlu memberitahu sistem | Event asinkron | Banyak consumer, tanpa blocking |
| Sinkronisasi data antar service | Event asinkron | Eventually consistent bisa diterima |
| Workflow kompleks lintas service | Pola saga | Koordinasi transaksi terdistribusi |
Manajemen Data
Database Per Service
Setiap microservice memiliki datanya dan mengeksposnya hanya melalui API-nya. Tidak ada service yang langsung query database service lain:
Order Service → orders_db (PostgreSQL)
Catalog Service → catalog_db (PostgreSQL)
Search Service → search_index (Elasticsearch)
Session Service → sessions (Redis)
Analytics Service → analytics_db (ClickHouse)
Isolasi ini berarti service bisa memilih teknologi database yang paling sesuai pola aksesnya. Ini juga berarti tidak ada service yang bisa merusak yang lain dengan memodifikasi data bersama.
Menangani Konsistensi Data
Di monolit, satu transaksi database memastikan konsistensi. Di microservices, Anda membutuhkan strategi berbeda:
Pola saga: Urutan transaksi lokal yang dikoordinasi melalui event. Jika langkah 3 gagal, transaksi kompensasi membatalkan langkah 1 dan 2.
Saga Pesanan:
1. Order Service: Buat pesanan (status: pending)
2. Payment Service: Proses pembayaran
→ Sukses: Lanjutkan
→ Gagal: Order Service membatalkan pesanan (kompensasi)
3. Inventory Service: Reservasi stok
→ Sukses: Order Service mengkonfirmasi pesanan
→ Gagal: Payment Service refund (kompensasi),
Order Service membatalkan pesanan (kompensasi)
Event sourcing: Simpan urutan event, bukan state saat ini. Service manapun bisa membangun ulang tampilannya tentang data dengan memainkan ulang event.
Observability
Distributed Tracing
Saat request mengalir melalui lima service, Anda perlu melacaknya end-to-end:
Request: GET /api/orders/ORD-2026-001
Trace ID: abc-123-def-456
├── API Gateway (12ms)
├── Order Service (45ms)
│ ├── Database query (8ms)
│ └── Customer Service call (25ms)
│ └── Database query (5ms)
├── Inventory Service call (18ms)
│ └── Database query (6ms)
└── Total: 75ms
Tools distributed tracing (Jaeger, Zipkin, AWS X-Ray) menginstrumentasi service Anda untuk mempropagasi konteks trace lintas batasan service.
Logging Terpusat
Log dari semua service harus mengalir ke sistem terpusat. Sertakan trace ID di setiap pesan log agar Anda bisa mengorelasikan log lintas service untuk satu request:
{
"timestamp": "2026-01-15T10:30:00.123Z",
"service": "order-service",
"traceId": "abc-123-def-456",
"level": "info",
"message": "Pesanan dibuat",
"orderId": "ORD-2026-001",
"customerId": "CUST-456"
}
Health Check dan Alerting
Setiap service membutuhkan:
- Health check endpoint (
/health) yang melaporkan status service - Metrics endpoint atau push-based metric collection (Prometheus)
- Alert pada error rate, latensi, dan utilisasi resource
- Runbook untuk skenario kegagalan umum
Jebakan Umum
Distributed Monolith
Jika setiap perubahan membutuhkan deployment terkoordinasi di beberapa service, Anda memiliki distributed monolith. Ini lebih buruk dari monolit biasa karena Anda memiliki semua kompleksitas sistem terdistribusi tanpa satupun manfaatnya.
Tanda-tanda distributed monolith:
- Service berbagi database
- Service harus di-deploy dalam urutan tertentu
- Perubahan sering melintas beberapa service
- Service memanggil satu sama lain secara sinkron dalam rantai panjang
Terlalu Banyak Service Terlalu Cepat
Memulai dengan 30 microservices saat Anda memiliki 15 engineer menciptakan kekacauan operasional. Overhead mengelola sebanyak itu service, database, pipeline deployment, dan dashboard monitoring membanjiri tim.
Mulai dengan monolith modular. Ekstrak service satu per satu saat batasan jelas muncul dan tooling operasional matang.
Mengabaikan Realitas Jaringan
Jaringan gagal. Latensi melonjak. Pesan terduplikasi atau hilang. Microservices yang tidak memperhitungkan ketidakandalan jaringan akan menunjukkan kegagalan aneh di production.
Setiap panggilan antar service membutuhkan:
- Timeout (jangan pernah menunggu selamanya)
- Retry dengan exponential backoff
- Circuit breaker untuk mencegah kegagalan beruntun
- Idempotensi untuk retry yang aman
Jalur Migrasi Pragmatis
Jika Anda beralih dari monolit ke microservices:
- Modularisasi monolit terlebih dahulu: Tetapkan batasan internal yang jelas, pisahkan layer akses data, definisikan API internal antar modul
- Bangun infrastruktur operasional: Monitoring, logging, CI/CD, orkestrasi container
- Ekstrak service pertama: Pilih sesuatu dengan batasan jelas dan coupling rendah ke sisanya. Notifikasi atau pencarian adalah titik awal umum.
- Belajar dan iterasi: Ekstraksi pertama akan mengungkap tantangan organisasional dan teknis. Atasi sebelum mengekstrak service berikutnya.
- Lanjutkan mengekstrak sesuai kebutuhan: Tidak setiap modul perlu menjadi service. Berhenti saat monolit yang tersisa bisa dikelola dan service yang diekstrak memberikan nilai.
Tujuannya bukan zero monolith. Tujuannya adalah arsitektur yang tepat untuk tim, produk, dan tahap pertumbuhan Anda.