Tampilan
Kenapa penting
Hampir setiap lowongan backend menyebut "microservices", tapi pertanyaan yang benar-benar diuji di pekerjaan bukan "bisa bikin microservice atau tidak", melainkan "perlukah dipecah, dan di mana garisnya". Memecah sistem terlalu dini membuat tim kecil tenggelam dalam urusan jaringan, deployment, dan debugging lintas service; terlambat memecah membuat puluhan developer saling injak di satu codebase. Bab ini memberi kamu kosakata dan kerangka berpikir untuk menimbang trade-off itu sebelum menulis satu baris kode Spring Cloud pun — sesuatu yang juga hampir pasti muncul di interview system design.
Konsep inti
Monolith: satu unit deployment
Monolith adalah aplikasi yang dibangun, dites, dan di-deploy sebagai satu unit — misalnya satu JAR Spring Boot yang berisi katalog, pesanan, pembayaran, dan notifikasi sekaligus, dengan satu database. Proyek mini M12 kamu adalah monolith, dan itu bukan hal buruk.
| Kelebihan monolith | Kelemahan saat tumbuh besar |
|---|---|
| Panggilan antarmodul = method call: cepat, tidak bisa "timeout" | Satu bug memori bisa menjatuhkan seluruh fitur |
Satu transaksi database untuk banyak tabel (@Transactional) | Build & test makin lama, deploy makin jarang |
| Debugging cukup dengan satu stack trace | Banyak tim di satu codebase → konflik merge & koordinasi rilis |
| Deployment, monitoring, dan lokal dev sederhana | Skala harus seluruh aplikasi, walau hanya satu fitur yang sibuk |
Masalah monolith yang sebenarnya jarang soal "satu JAR", melainkan tidak adanya batas: class pesanan memanggil repository pembayaran langsung, entity dipakai lintas fitur, sampai perubahan kecil merambat ke mana-mana. Ini disebut big ball of mud.
Microservices: banyak unit yang di-deploy mandiri
Microservices memecah sistem menjadi beberapa service kecil yang masing-masing:
- memiliki satu kapabilitas bisnis (misal "pembayaran"),
- punya data sendiri — service lain tidak boleh membaca tabelnya langsung,
- berkomunikasi lewat jaringan (HTTP/gRPC/event),
- bisa di-deploy dan di-skala mandiri oleh tim pemiliknya.
Manfaatnya nyata: tim bisa rilis tanpa menunggu tim lain, service yang sibuk (misal katalog saat promo) bisa di-skala sendiri, dan kegagalan bisa diisolasi. Tapi biayanya juga nyata:
| Biaya microservices | Contoh konkret |
|---|---|
| Panggilan jaringan | Latency, timeout, retry, serialisasi JSON |
| Konsistensi data | Tidak ada transaksi lintas database → perlu saga/event (dibahas di bab berikutnya) |
| Operasional | Banyak pipeline, container, konfigurasi, secret |
| Observability | Satu request melewati 5 service → butuh tracing & log terkorelasi |
| Testing | Contract test, environment integrasi, data uji lintas service |
Satu ilustrasi biaya yang sering diremehkan: availability berantai. Bila satu request harus melewati beberapa service secara sinkron, peluang suksesnya adalah perkalian peluang tiap service.
java
public class AvailabilityChain {
public static void main(String[] args) {
double perService = 0.999; // 99,9% per service
int[] chainLengths = {1, 3, 5, 10};
for (int n : chainLengths) {
double total = Math.pow(perService, n);
double downtimeMinutesPerMonth = (1 - total) * 30 * 24 * 60;
System.out.printf("%2d service berantai -> availability %.4f%%, downtime ~%.0f menit/bulan%n",
n, total * 100, downtimeMinutesPerMonth);
}
}
}text
1 service berantai -> availability 99.9000%, downtime ~43 menit/bulan
3 service berantai -> availability 99.7003%, downtime ~129 menit/bulan
5 service berantai -> availability 99.5010%, downtime ~216 menit/bulan
10 service berantai -> availability 99.0045%, downtime ~430 menit/bulanSepuluh service yang masing-masing "sangat andal" menghasilkan sistem dengan downtime sepuluh kali lipat. Inilah alasan microservices yang baik meminimalkan rantai panggilan sinkron.
Fallacies of distributed computing
Peter Deutsch dan rekan di Sun Microsystems merumuskan delapan asumsi keliru yang sering dibuat developer saat pertama kali membangun sistem terdistribusi:
- Jaringan itu andal.
- Latency itu nol.
- Bandwidth tidak terbatas.
- Jaringan itu aman.
- Topologi tidak berubah.
- Hanya ada satu administrator.
- Biaya transport itu nol.
- Jaringan itu homogen.
Di monolith, inventoryService.reserve(sku, qty) hampir pasti berhasil dalam mikrodetik. Begitu itu menjadi panggilan HTTP, panggilan yang sama bisa lambat, gagal di tengah jalan, atau berhasil di server tapi responsnya hilang — sehingga kamu tidak tahu apakah stok sudah dipesan. Setiap baris kode yang memanggil service lain harus menjawab: berapa timeout-nya, apa yang terjadi kalau gagal, dan apakah aman di-retry? Resilience4j dan idempotency key yang akan kamu pelajari di M14 adalah jawaban teknis atas fallacies ini.
Modular monolith dan Spring Modulith
Ada jalan tengah yang sering menjadi pilihan terbaik: modular monolith — satu unit deployment, tetapi dibagi menjadi modul dengan batas tegas. Setiap modul punya API publik kecil, detail internalnya tersembunyi, dan modul lain hanya boleh memakai API itu (atau mendengarkan event-nya).
text
id.jalurjava.shop
├── ShopApplication.java
├── catalog/ <- modul: hanya class di paket ini yang publik
│ ├── CatalogApi.java
│ └── internal/ <- tidak boleh dipakai modul lain
├── order/
│ ├── OrderApi.java
│ ├── OrderPlaced.java <- event publik
│ └── internal/
└── payment/
└── internal/Spring Modulith (proyek resmi Spring) membuat batas ini bisa diverifikasi: setiap paket langsung di bawah paket aplikasi dianggap modul, dan test berikut gagal bila ada modul yang mengakses internal modul lain atau terjadi dependensi siklik.
java
// Potongan — test arsitektur dengan Spring Modulith
class ModularityTests {
@Test
void verifiesModuleBoundaries() {
ApplicationModules.of(ShopApplication.class).verify();
}
}Modul juga bisa berkomunikasi lewat event aplikasi alih-alih memanggil langsung, sehingga kelak modul itu lebih mudah diekstrak menjadi service sendiri:
java
// Potongan — modul payment bereaksi pada event dari modul order
@Component
class PaymentOnOrderPlaced {
@ApplicationModuleListener
void on(OrderPlaced event) {
// buat tagihan untuk event.orderId()
}
}Dengan modular monolith kamu mendapat sebagian besar manfaat desain microservices (batas jelas, kepemilikan per modul) tanpa membayar biaya jaringan dan operasional — setidaknya sampai ada alasan kuat untuk memisahkan deployment.
Conway's law
Melvin Conway (1968) mengamati bahwa organisasi cenderung menghasilkan desain sistem yang meniru struktur komunikasi organisasinya. Bila tim dibagi per lapisan teknis (frontend, backend, DBA), sistemnya cenderung terbagi per lapisan, dan setiap fitur butuh tiga tim. Bila tim dibagi per domain (tim Pesanan, tim Pembayaran), batas service cenderung mengikuti domain itu.
Konsekuensinya praktis: microservices tanpa tim yang memiliki service-nya dari ujung ke ujung hanya memindahkan koordinasi dari merge conflict ke rapat lintas tim. Banyak perusahaan sengaja menyusun tim lebih dulu sesuai arsitektur yang diinginkan — disebut inverse Conway maneuver.
Kapan memecah, kapan jangan
| Sinyal layak memecah | Sinyal sebaiknya jangan (dulu) |
|---|---|
| Beberapa tim terhambat rilis karena satu codebase | Tim kecil (≤ 1–2 tim) |
| Satu bagian butuh skala/teknologi sangat berbeda | Domain masih berubah tiap minggu (MVP) |
| Bagian tertentu butuh isolasi kegagalan atau keamanan ketat (misal pembayaran) | Belum ada CI/CD, monitoring, dan tracing yang matang |
| Batas modul sudah stabil dan jarang dilanggar | Modul di monolith pun masih saling tembus |
| Kebutuhan rilis berbeda drastis antarbagian | Alasannya "karena tren" atau "biar CV bagus" |
Aturan praktis: kalau kamu tidak bisa membangun modular monolith yang rapi, kamu juga tidak akan bisa membangun microservices yang rapi — hasilnya distributed monolith: service terpisah yang harus di-deploy bersamaan karena saling bergantung erat, dengan semua kerugian kedua dunia.
Strangler fig: migrasi bertahap
Ketika memecah memang perlu, hindari big bang rewrite. Pola strangler fig (dinamai dari pohon ara yang tumbuh melilit pohon inang) memindahkan fungsi satu per satu:
- Pasang façade/routing (API gateway atau reverse proxy) di depan monolith; awalnya semua lalu lintas diteruskan ke monolith.
- Bangun service baru untuk satu kapabilitas, misal notifikasi.
- Arahkan rute terkait (
/notifications/**) ke service baru; sisanya tetap ke monolith. - Pantau, bandingkan hasil, lalu hapus kode lama dari monolith.
- Ulangi untuk kapabilitas berikutnya.
Mulailah dari bagian yang batasnya jelas dan risikonya rendah (notifikasi, laporan), bukan dari inti yang paling kusut.
Contoh di dunia backend
Sebuah toko online punya monolith Spring Boot dengan modul katalog, pesanan, pembayaran, dan notifikasi. Setelah modularisasi, modul pesanan tidak lagi memanggil notifikasi secara langsung, melainkan menerbitkan event:
java
// Potongan — modul order (monolith yang sudah modular)
@Service
public class OrderService {
private final OrderRepository orders;
private final ApplicationEventPublisher events;
public OrderService(OrderRepository orders, ApplicationEventPublisher events) {
this.orders = orders;
this.events = events;
}
@Transactional
public OrderId placeOrder(PlaceOrderCommand command) {
Order order = Order.create(command.customerId(), command.lines());
orders.save(order);
events.publishEvent(new OrderPlaced(order.id(), order.customerId(), order.total()));
return order.id();
}
}Karena modul notifikasi hanya bergantung pada event OrderPlaced, mengekstraknya menjadi service terpisah cukup mengganti mekanisme pengiriman event (dari event in-process ke broker seperti Kafka) dan memasang rute strangler di gateway:
yaml
# Potongan — rute Spring Cloud Gateway selama migrasi strangler fig
spring:
cloud:
gateway:
server:
webflux:
routes:
- id: notification-service
uri: http://notification-service:8080
predicates:
- Path=/notifications/**
- id: legacy-monolith
uri: http://shop-monolith:8080
predicates:
- Path=/**Rute yang lebih spesifik ditulis lebih dulu; semua yang belum dipindah tetap jatuh ke monolith. Detail gateway dibahas di M14 — di sini cukup pahami polanya: lalu lintas dipindah per rute, bukan sekaligus.
Pertanyaan interview
1. Apa perbedaan modular monolith dan microservices, dan kapan kamu memilih yang pertama?
Keduanya membagi sistem per kapabilitas bisnis dengan batas tegas. Bedanya, modular monolith tetap satu unit deployment dan satu proses, sehingga komunikasi antarmodul berupa method call atau event in-process dan transaksi lokal masih tersedia. Saya memilih modular monolith saat tim masih kecil, domain belum stabil, atau infrastruktur operasional (CI/CD, observability) belum matang — lalu mengekstrak modul menjadi service bila ada alasan konkret seperti kebutuhan skala atau kecepatan rilis yang berbeda.
2. Apa itu distributed monolith dan bagaimana mengenalinya?
Distributed monolith adalah sistem yang secara fisik terpecah menjadi banyak service, tetapi secara logis masih saling terikat erat. Tandanya: service harus di-deploy bersamaan, satu perubahan menyentuh banyak repo, service berbagi database atau tabel, dan satu request memicu rantai panggilan sinkron panjang. Hasilnya biaya microservices tanpa manfaat kemandirian deployment.
3. Bagaimana kamu memigrasikan monolith besar ke microservices tanpa menghentikan bisnis?
Dengan strangler fig: pasang façade/routing di depan monolith, rapikan dulu batas modul di dalam monolith, lalu ekstrak satu kapabilitas yang batasnya jelas dan risikonya rendah. Lalu lintas dipindah per rute, dipantau, dan bisa dikembalikan ke monolith bila bermasalah. Data dipindah bertahap — misalnya service baru menjadi pemilik tabelnya dan monolith mengakses lewat API — sampai kode lama bisa dihapus.
Jebakan umum
- Memecah karena tren. Microservices menyelesaikan masalah organisasi dan skala; tanpa masalah itu, kamu hanya menambah kompleksitas.
- Berbagi database antar-service. Dua service yang menulis tabel yang sama tidak benar-benar mandiri — perubahan skema memaksa keduanya rilis bersama.
- Memperlakukan panggilan HTTP seperti method lokal. Tanpa timeout, retry yang aman, dan fallback, satu service lambat bisa menjatuhkan semuanya.
- Rantai panggilan sinkron panjang. Setiap hop menambah latency dan menurunkan availability secara multiplikatif.
- Big bang rewrite. Menulis ulang seluruh sistem lalu mengganti dalam sekali jalan hampir selalu molor dan berisiko; pakai strangler fig.
- Memotong per lapisan teknis (service "database", service "validasi") alih-alih per kapabilitas bisnis.
Ringkasan
- Monolith = satu unit deployment; masalah sebenarnya biasanya bukan ukurannya, tapi tidak adanya batas modul.
- Microservices memberi deployment dan skala mandiri, dibayar dengan biaya jaringan, konsistensi data, operasional, dan observability.
- Delapan fallacies of distributed computing mengingatkan: jaringan bisa gagal, lambat, dan berubah — desain harus siap untuk itu.
- Modular monolith (diverifikasi misalnya dengan Spring Modulith) sering menjadi titik awal terbaik.
- Conway's law: batas service mengikuti struktur tim; pecah sistem bila tim dan batas domain sudah jelas.
- Migrasi dilakukan bertahap dengan strangler fig, bukan big bang rewrite.