Modular monolith sebaiknya dipilih daripada microservices ketika Anda membutuhkan pemisahan domain yang rapi, tetapi belum memiliki kebutuhan kuat untuk deployment independen, isolasi kegagalan tingkat layanan, atau skalabilitas yang benar-benar berbeda per komponen. Dalam banyak kasus, terutama pada startup dan tim produk yang masih berkembang, modular monolith memberi kecepatan delivery lebih baik dengan biaya operasional yang jauh lebih rendah.

Microservices baru masuk akal ketika batas domain sudah relatif stabil, organisasi memang membutuhkan otonomi tim yang tinggi, dan tim siap menanggung kompleksitas tambahan: jaringan, observabilitas, deployment terpisah, orkestrasi, versioning API, dan failure mode yang lebih sulit didiagnosis. Jika kebutuhan itu belum nyata, memecah sistem terlalu cepat sering menghasilkan kompleksitas tanpa manfaat yang sepadan.

Apa yang Dimaksud dengan Modular Monolith?

Modular monolith adalah aplikasi yang dideploy sebagai satu unit, tetapi di dalamnya dibagi menjadi modul-modul dengan batas tanggung jawab yang jelas. Setiap modul idealnya memiliki:

  • logika domain sendiri,
  • kontrak antarmuka yang eksplisit,
  • akses data yang terkontrol,
  • uji otomatis yang dapat dijalankan per modul.

Perbedaannya dengan monolith yang berantakan bukan pada jumlah proses, melainkan pada disiplin boundary di level kode. Anda tetap mendapat banyak manfaat modularitas tanpa harus langsung menghadapi kompleksitas sistem terdistribusi.

Ciri modular monolith yang sehat

  • Dependensi antar modul dibatasi dan mudah dilacak.
  • Modul tidak saling mengakses tabel atau repository internal secara sembarangan.
  • Integrasi antarmodul dilakukan melalui service interface, event internal, atau application layer yang jelas.
  • Build, test, dan review masih bisa difokuskan ke area perubahan tertentu.

Kapan Memilih Modular Monolith daripada Microservices

1. Tim masih kecil atau kapasitas operasional terbatas

Jika tim engineering berukuran kecil, misalnya hanya beberapa backend engineer dengan dukungan DevOps yang terbatas, microservices biasanya terlalu mahal secara operasional. Setiap service menambah kebutuhan:

  • pipeline CI/CD sendiri,
  • logging dan metrics per service,
  • trace lintas jaringan,
  • manajemen secret dan konfigurasi,
  • monitoring health check dan alerting,
  • strategi deployment, rollback, dan compatibility.

Dalam modular monolith, semua kebutuhan itu tetap ada, tetapi dikelola untuk satu artefak deployment. Beban koordinasi jauh lebih rendah.

2. Domain boundary belum stabil

Jika Anda masih sering mengubah model bisnis, memecah domain ke banyak service terlalu dini bisa berisiko. Boundary yang salah pada microservices mahal untuk diperbaiki karena perubahan menyentuh:

  • kontrak API,
  • skema event,
  • ownership data,
  • alur autentikasi dan otorisasi,
  • mekanisme retry dan idempotency.

Dengan modular monolith, Anda masih bisa bereksperimen dengan pemisahan domain di level kode tanpa harus memindahkan batas tersebut ke level jaringan dan deployment.

3. Kecepatan delivery lebih penting daripada independensi rilis

Banyak tim mengira microservices selalu mempercepat delivery. Kenyataannya, jika satu perubahan fitur menyentuh beberapa service, siklus delivery justru melambat karena perlu sinkronisasi perubahan API, deployment berurutan, dan validasi kompatibilitas. Modular monolith sering lebih cepat untuk:

  • fitur yang melintasi banyak domain,
  • refactor model data,
  • perubahan transaksi bisnis yang membutuhkan konsistensi kuat,
  • debugging end-to-end yang sering terjadi pada tahap awal produk.

Jika mayoritas perubahan masih bersifat lintas-domain, satu codebase dan satu deployment unit biasanya lebih efisien.

4. Kebutuhan observabilitas belum bisa didukung dengan baik

Microservices tanpa observabilitas yang matang adalah resep masalah produksi yang sulit diurai. Begitu request melewati beberapa service, Anda butuh minimal:

  • correlation ID atau trace ID,
  • centralized logging,
  • distributed tracing,
  • metrics per endpoint dan dependency,
  • alerting berbasis SLO atau error budget.

Jika tim belum siap menjalankan praktik ini dengan disiplin, modular monolith memberi jalur yang lebih aman. Masalah masih terjadi, tetapi biasanya lebih mudah didiagnosis karena call path ada dalam satu proses.

5. Beban traffic tinggi belum berarti perlu microservices

Skalabilitas bukan hanya soal memecah aplikasi. Banyak sistem dapat diskalakan cukup lama dengan:

  • optimasi query dan indexing database,
  • caching,
  • queue untuk pekerjaan asinkron,
  • horizontal scaling untuk aplikasi monolith,
  • pemecahan read/write path bila perlu.

Microservices lebih relevan jika pola scaling antar domain benar-benar berbeda, misalnya modul pencarian perlu autoscaling agresif sementara modul administrasi hampir tidak berubah bebannya.

Trade-off Utama: Modular Monolith vs Microservices

AspekModular MonolithMicroservices
DeploymentSatu artefak, lebih sederhana, rollback lebih mudahBanyak artefak, butuh orkestrasi dan compatibility management
Kompleksitas operasionalLebih rendahLebih tinggi: service discovery, networking, observability, config
Kecepatan delivery awalBiasanya lebih cepatBisa lebih lambat jika boundary belum matang
Isolasi kegagalanTerbatas pada satu prosesLebih baik jika didesain dengan benar, tetapi ada failure mode baru
Konsistensi dataLebih mudah, transaksi lokal lebih sederhanaLebih sulit, sering butuh eventual consistency
Skalabilitas per domainTerbatas pada unit aplikasi yang samaLebih fleksibel untuk scaling independen
Independensi rilisTerbatasLebih tinggi jika tim dan kontrak service matang
Maintainability jangka panjangBaik jika boundary internal dijaga ketatBaik jika organisasi siap; buruk jika service terlalu kecil dan terikat erat
DebuggingRelatif lebih mudahLebih sulit karena masalah jaringan, timeout, retry, partial failure
Biaya infrastrukturLebih rendahLebih tinggi

Sinyal Teknis dan Organisasi untuk Tiap Pilihan

Sinyal yang mendukung modular monolith

  • Tim engineering kecil sampai menengah dan belum punya platform team khusus.
  • Batas domain masih berubah karena produk belum stabil.
  • Sebagian besar fitur menyentuh banyak area bisnis sekaligus.
  • Kebutuhan transaksi konsisten dalam satu request masih dominan.
  • Frekuensi deploy belum terlalu tinggi atau masih bisa dikoordinasikan.
  • Kapasitas observabilitas, incident response, dan automation masih terbatas.
  • Prioritas utama adalah validasi produk dan kecepatan iterasi.

Sinyal yang mendukung microservices

  • Beberapa domain sudah jelas, stabil, dan memiliki owner tim yang tegas.
  • Ada kebutuhan kuat untuk independent release antardomain.
  • Pola beban kerja sangat berbeda antar komponen dan perlu scaling terpisah.
  • Isolasi kegagalan menjadi kebutuhan nyata, bukan asumsi.
  • Tim sudah siap dengan tracing, metrics, logging terpusat, dan on-call yang matang.
  • Ketergantungan antar tim menjadi bottleneck organisasi yang serius.
  • Sistem cukup besar sehingga satu codebase menjadi hambatan build, test, atau ownership.

Biaya Operasional yang Sering Diremehkan pada Microservices

Banyak diskusi arsitektur fokus pada fleksibilitas, tetapi mengabaikan biaya harian. Pada microservices, biaya ini muncul terus-menerus:

  • CI/CD: lebih banyak pipeline, lebih banyak status deployment yang harus dipantau.
  • Observabilitas: log dan trace meningkat drastis, termasuk biaya penyimpanan dan analisis.
  • Operasional jaringan: timeout, retry storm, DNS issue, TLS, dan latensi antarservice.
  • Kontrak API: versioning, backward compatibility, dan sinkronisasi perubahan.
  • Data ownership: duplikasi data, sinkronisasi event, dan rekonsiliasi ketika ada drift.
  • Incident handling: akar masalah sering tersembunyi di rantai dependency.

Jika tim belum merasakan manfaat nyata dari biaya ini, modular monolith biasanya adalah keputusan yang lebih rasional.

Maintainability Jangka Panjang: Modular Monolith Bisa Sangat Baik, Bisa Juga Gagal

Kesalahan umum adalah menganggap modular monolith otomatis mudah dirawat. Tidak. Ia hanya efektif jika boundary modul dijaga dengan tegas. Tanpa itu, yang terjadi adalah monolith besar dengan coupling tinggi.

Prinsip maintainability untuk modular monolith

  • Setiap modul punya API internal yang eksplisit.
  • Database access dibatasi melalui repository atau service milik modul itu sendiri.
  • Shared library dibuat sangat hemat; jangan memindahkan semua logika ke folder common.
  • Gunakan pengujian per modul dan integration test yang fokus pada kontrak antarmodul.
  • Ukur dependency antar modul; jika semua modul saling impor, modularitas hanya ilusi.

Contoh struktur modul

src/
  modules/
    catalog/
      application/
      domain/
      infrastructure/
      api/
    ordering/
      application/
      domain/
      infrastructure/
      api/
    billing/
      application/
      domain/
      infrastructure/
      api/
  shared/
    kernel/
      logging/
      events/
      auth/

Struktur seperti ini membantu tim menjaga ownership per domain meskipun aplikasi tetap satu deployment unit.

Contoh integrasi antarmodul yang lebih sehat

// ordering/application/place_order.js
async function placeOrder(input, deps) {
  const product = await deps.catalogService.getProductForSale(input.productId);
  if (!product || !product.isAvailable) throw new Error('Produk tidak tersedia');

  const order = deps.orderFactory.create(input, product.price);
  await deps.orderRepository.save(order);

  deps.domainEvents.publish({
    type: 'OrderPlaced',
    orderId: order.id,
    customerId: order.customerId,
    total: order.total
  });

  return order;
}

Contoh di atas menunjukkan modul ordering tidak langsung membaca tabel internal modul catalog. Ini penting agar batas modul tetap terjaga dan nanti lebih mudah diekstrak bila benar-benar perlu menjadi service terpisah.

Skenario Nyata

Startup tahap awal

Bayangkan startup SaaS B2B dengan 5 engineer. Fitur utama masih berubah cepat: onboarding, billing, permission, dan reporting sering disentuh bersamaan. Tim belum punya SRE atau platform engineer, dan incident masih ditangani langsung oleh developer yang sedang on-call bergantian.

Dalam kondisi ini, modular monolith hampir selalu lebih masuk akal karena:

  • fitur lintas domain masih sering berubah,
  • konsistensi data dalam satu transaksi masih penting,
  • satu pipeline deployment cukup,
  • debugging lebih cepat,
  • biaya infra dan tooling tetap terkendali.

Microservices pada tahap ini biasanya hanya menambah koordinasi, bukan mengurangi bottleneck.

Tim growth stage

Sekarang bayangkan produk sudah matang, pengguna bertambah, dan tim berkembang menjadi 25-40 engineer. Domain seperti billing, authentication, search, dan notifications mulai punya ritme perubahan yang berbeda. Beberapa domain butuh SLA lebih ketat dan ada tuntutan rilis independen karena banyak squad bekerja paralel.

Di tahap ini, pendekatan yang sering efektif adalah:

  • mempertahankan core business dalam modular monolith,
  • mengeluarkan domain yang benar-benar punya kebutuhan khusus menjadi service terpisah,
  • memilih ekstraksi berdasarkan bottleneck nyata, bukan mode arsitektur.

Contohnya, modul notifikasi atau pencarian sering lebih aman diekstrak lebih dulu daripada billing inti, karena coupling bisnisnya biasanya lebih kecil dan toleransi eventual consistency lebih tinggi.

Anti-Pattern Umum

1. Memecah menjadi microservices karena ukuran codebase mulai besar

Codebase besar bukan satu-satunya sinyal. Sering kali masalah sebenarnya adalah struktur modul buruk, test lambat, atau ownership kode tidak jelas. Memecah service tanpa memperbaiki boundary biasanya hanya memindahkan masalah.

2. Menganggap microservices otomatis memberi skalabilitas

Jika bottleneck utama ada di database tunggal, query yang tidak efisien, atau cache yang buruk, memecah aplikasi belum tentu membantu. Anda hanya menambahkan hop jaringan di atas bottleneck yang sama.

3. Shared database antar microservice

Ini salah satu anti-pattern paling umum. Jika beberapa service tetap bergantung pada skema database yang sama, Anda kehilangan otonomi service tetapi tetap menanggung biaya sistem terdistribusi.

4. Modular monolith palsu

Aplikasi terlihat modular di folder, tetapi modul bebas saling memanggil repository atau model ORM internal. Ini membuat refactor dan ekstraksi service di masa depan menjadi sulit.

5. Service terlalu kecil tanpa alasan domain yang kuat

Terlalu banyak service kecil meningkatkan biaya koordinasi, deployment, dan observabilitas. Ukuran service seharusnya mengikuti batas domain dan kebutuhan operasional, bukan preferensi abstrak.

Checklist Keputusan: Pilih Modular Monolith atau Microservices?

Gunakan checklist berikut saat mengevaluasi arsitektur. Jika sebagian besar jawaban ada di kolom kiri, pilih modular monolith. Jika banyak jawaban ada di kolom kanan, pertimbangkan microservices.

PertanyaanCondong ke Modular MonolithCondong ke Microservices
Apakah batas domain sudah stabil?BelumYa, relatif stabil
Apakah tim butuh rilis independen per domain?Tidak terlaluYa, kebutuhan nyata
Apakah tim siap mengelola tracing, metrics, alerting, dan on-call lintas service?BelumSudah
Apakah sebagian besar perubahan fitur masih lintas domain?YaTidak, makin terpisah
Apakah setiap domain punya kebutuhan scaling yang berbeda signifikan?TidakYa
Apakah isolasi kegagalan antar domain adalah kebutuhan produksi yang terbukti?BelumYa
Apakah kapasitas platform/DevOps cukup untuk banyak service?TerbatasMemadai
Apakah konsistensi transaksi lintas modul masih dominan?YaTidak, eventual consistency dapat diterima

Checklist implementasi bila memilih modular monolith

  1. Tentukan modul berdasarkan capability bisnis, bukan layer teknis semata.
  2. Definisikan API internal per modul dan larang akses langsung ke detail implementasi modul lain.
  3. Pasang aturan code review untuk menjaga dependency tetap searah dan minimal.
  4. Tambahkan event internal untuk integrasi longgar di dalam aplikasi.
  5. Siapkan observabilitas dasar sejak awal: structured logging, metrics, dan correlation ID.
  6. Identifikasi modul yang mungkin diekstrak di masa depan, tetapi jangan ekstrak sebelum ada alasan kuat.

Checklist implementasi bila memilih microservices

  1. Pastikan ownership domain dan ownership operasional per service jelas.
  2. Hindari shared database; tetapkan data ownership yang tegas.
  3. Desain kontrak API dan event untuk kompatibilitas ke belakang.
  4. Siapkan distributed tracing, centralized logging, metrics, dashboard, dan alerting sebelum skala service bertambah.
  5. Gunakan timeout, retry, circuit breaker, dan idempotency dengan disiplin.
  6. Document failure mode dan jalur degradasi layanan.

Strategi Evolusi yang Praktis

Pendekatan yang sering paling aman adalah mulai dari modular monolith, lalu ekstrak secara selektif. Ini bukan kompromi lemah, melainkan strategi untuk menunda kompleksitas sampai manfaatnya benar-benar jelas.

Ekstraksi ke microservices sebaiknya dilakukan ketika ada kombinasi sinyal berikut:

  • bottleneck scaling spesifik pada satu modul,
  • owner tim yang jelas,
  • boundary data dan domain sudah stabil,
  • kebutuhan deployment independen tidak bisa dihindari,
  • tim operasional siap mengelola service tambahan.

Jika Anda belum bisa menjelaskan service mana yang perlu dipisah, mengapa perlu dipisah, metrik apa yang akan membaik, dan biaya operasional apa yang siap ditanggung, kemungkinan besar Anda belum perlu microservices.

Kesimpulan

Kapan memilih modular monolith daripada microservices? Pilih modular monolith ketika tim masih terbatas, domain boundary belum matang, kebutuhan observabilitas terdistribusi belum siap, dan kecepatan delivery lebih penting daripada independensi rilis. Ini sering menjadi pilihan teknik yang lebih efisien, bukan pilihan sementara yang kalah canggih.

Pilih microservices ketika domain sudah stabil, organisasi butuh otonomi tim yang nyata, pola scaling antar domain berbeda, dan tim siap menanggung kompleksitas operasional sistem terdistribusi. Keputusan yang baik bukan soal mengikuti tren arsitektur, tetapi soal menempatkan kompleksitas di titik yang memberi nilai paling besar.