Dulu sederhana: seluruh aplikasi Anda berjalan dalam satu proses. Satu basis kode, satu deployment, semuanya dalam memori yang sama. Monolit. Tidak seksi, tetapi dapat dikelola.
Microservices berjanji untuk melakukan lebih baik. Layanan terpisah. Deployment independen. API gateway. Kubernetes. Service mesh. Semuanya dibagi dengan rapi. Tim otonom.
Sampai Anda melihat perilaku runtime.
Kemudian Anda menemukan bahwa tindakan pengguna sederhana bergantung pada empat belas layanan, tiga database, dua antrian, penyedia identitas eksternal, dan lapisan caching yang diam-diam membuat perbedaan antara 200ms dan 12 detik.
Monolitnya tidak hilang. Sekarang hanya didistribusikan di seluruh jaringan. Itu hanya membuat coupling lebih sulit dilihat.
Ilusi kemandirian
Microservices dijual sebagai komponen yang dapat di-deploy secara independen. Secara teori itu benar. Dan ya — banyak tim menggunakan message broker dan pola berbasis peristiwa untuk memisahkan layanan. Itu membantu.
Tetapi lihat jalur yang sebenarnya ditempuh pengguna: panggilan API yang mengharapkan respons. Di sana, async jarang menjadi pilihan. Pesan async mengurangi coupling runtime, tetapi tidak menghilangkan coupling bisnis. Pengguna yang melakukan pesanan menginginkan konfirmasi — bukan “kami akan memberi tahu nanti.”
Dan tepat di jalur-jalur itulah saya melihat hal yang sama muncul di hampir setiap proyek: Layanan A menunggu secara sinkron pada B untuk otorisasi. B memeriksa harga dengan C. C mengambil inventaris dari D. Sementara itu pengguna masih menunggu.
Di atas kertas itu adalah empat layanan terpisah. Saat runtime itu adalah satu rantai.
Dan rantai memiliki properti yang tidak menyenangkan: mereka hanya sekuat mata rantai terlemahnya.
Panggilan jaringan bukan panggilan fungsi
Perbedaan ini secara struktural diremehkan.
Panggilan fungsi dalam monolit dapat diprediksi: memori bersifat lokal, latensi dalam mikrosecond, hampir tidak ada faktor eksternal. Panggilan jaringan memperkenalkan model kegagalan yang sama sekali berbeda: latensi, timeout, retry, masalah DNS, TLS handshake, connection pooling, kegagalan sementara.
Apa yang membutuhkan milidetik secara lokal tiba-tiba menjadi thread exhaustion dan kegagalan beruntun di bawah beban. Bukan karena kodenya buruk, tetapi karena jaringan tidak berperilaku seperti memori — dan arsitektur Anda bertindak seolah-olah demikian.
Dan bagian yang berbahaya: degradasi itu jarang berlanjut secara linear. Ini menciptakan antrian, retry, persaingan sumber daya, dan backpressure di setiap sistem yang bergantung padanya. Matematika di baliknya sederhana tetapi tak kenal ampun — dari teori antrian kita tahu bahwa waktu tunggu tumbuh secara eksponensial dengan beban. Layanan pada 50% utilisasi memiliki waktu tunggu sama dengan waktu pemrosesannya. Pada 90% nilainya sembilan kali lebih lama. Pada 95% sembilan belas kali.
Itu berarti layanan yang menjadi 300ms lebih lambat tidak menyebabkan 300ms latensi tambahan. Ini mendorong dirinya sendiri dan semua yang bergantung padanya melewati titik kritis. Antrian terisi, retry menumpuk, thread habis. Satu tautan yang lambat dapat menarik sepuluh layanan yang sehat ke bawah. Dan sumber yang sebenarnya hampir tidak pernah ada di tempat alarm berbunyi.
Itulah yang dimaksud dengan Fallacies of Distributed Computing. Namun saya melihatnya kembali setiap kali, dibungkus dalam diagram arsitektur yang bersih.
Bagaimana monolit terdistribusi muncul
Jarang berasal dari insinyur yang buruk. Ini muncul karena tim membagi fungsionalitas sepanjang garis logis — pengguna, pesanan, inventaris, pembayaran, notifikasi — sementara proses bisnis yang mendasarinya tetap terikat erat.
Menempatkan pesanan masih membutuhkan: autentikasi, pemeriksaan inventaris, perhitungan harga, pembayaran, dan notifikasi. Hanya ketergantungan itu sekarang berjalan melalui HTTP atau gRPC daripada dalam proses.
Coupling tidak hilang. Ia menjadi lebih sulit dilihat, lebih sulit di-debug, dan lebih sensitif terhadap latensi.
Observability bukan lagi kemewahan
Dalam monolit Anda masih bisa men-debug secara linear. Stack trace memberi tahu Anda dengan cukup baik di mana sesuatu salah. Dengan microservices kemewahan itu tidak ada.
Pesan kesalahan di Layanan A bisa disebabkan oleh timeout di C, penyimpanan lambat di D, backpressure dalam antrian, atau ketergantungan yang “setengah rusak” — cukup bekerja untuk tidak memicu alarm, tetapi terlalu lambat untuk menjaga sistem tetap sehat.
Distributed tracing, correlation ID, dan structured logging bukan opsional di sini. Mereka adalah perbedaan antara “kami memahami sistem kami sendiri” dan “kami men-deploy dan berharap yang terbaik.”
Lebih banyak layanan ≠arsitektur yang lebih baik
Microservices memecahkan masalah nyata: otonomi tim, deployment independen, skalabilitas terarah, isolasi kesalahan.
Tetapi mereka memperkenalkan setidaknya kompleksitas yang sama: perilaku jaringan, versioning, koordinasi deployment, ketergantungan operasional, dan ketidakstabilan runtime.
Trade-off itu terlalu jarang dibuat eksplisit. Pertanyaannya seharusnya bukan “bagaimana kita membagi aplikasi?”, tetapi “apakah skala dan ukuran tim kita membenarkan kompleksitas operasional ini?” Untuk banyak organisasi jawaban jujurnya adalah: tidak.
Apa yang bisa Anda lakukan
Mengenali monolit terdistribusi adalah langkah pertama. Tetapi mengenalinya tanpa bertindak hanya menghasilkan sinisme. Beberapa pola yang saya lihat bekerja efektif dalam praktik:
Jadikan ketergantungan asinkron di mana memungkinkan. Tidak setiap permintaan perlu ditangani secara sinkron. Pola berbasis peristiwa dengan message broker memisahkan layanan dalam waktu — dan oleh karena itu dalam ketersediaan. Pesanan tidak perlu menunggu sampai notifikasi terkirim.
Desain untuk kegagalan, tidak hanya untuk keberhasilan. Circuit breaker, bulkhead, timeout, dan fallback bukan optimasi. Mereka adalah arsitektur dasar saat Anda berkomunikasi melalui jaringan.
Berani menggambar ulang batas. Jika dua layanan selalu harus di-deploy bersama, selalu gagal bersama, dan selalu saling menunggu secara sinkron — maka itu bukan dua layanan. Maka itu satu layanan dengan panggilan jaringan di antaranya. Ambil kesimpulan dan gabungkan.
Investasikan dalam observability sebelum membutuhkannya. Menambahkan distributed tracing dan structured logging setelah fakta ke lanskap tiga puluh layanan adalah mimpi buruk. Mulailah dengan layanan kedua.
Perbedaan yang sesungguhnya
Arsitektur terdistribusi yang baik tidak membedakan dirinya ketika semuanya bekerja. Ia membedakan dirinya ketika bagian-bagian sistem tidak lagi bekerja.
Pertanyaannya bukan apakah sesuatu gagal. Pada skala yang cukup sesuatu selalu gagal. Pertanyaannya adalah apakah sistem Anda dirancang untuk menangani itu — atau apakah satu database yang lambat menarik seluruh lanskap ke bawah.
Itulah perbedaan antara microservices sebagai pilihan arsitektur dan microservices sebagai hype. Dan percakapan jujur tentang itu dimulai dengan berani melihat perilaku runtime daripada diagram arsitektur.