Monolit modular sering menjadi pilihan paling rasional ketika aplikasi harus berjalan di hardware lama, VPS hemat biaya, atau lingkungan dengan pertumbuhan traffic yang belum tinggi. Dalam kondisi seperti ini, tantangan utamanya bukan sekadar skalabilitas teoritis, melainkan efisiensi CPU, RAM, storage, kemudahan deployment, dan kemampuan tim kecil untuk mengoperasikan sistem tanpa beban infrastruktur berlebihan.

Tren penggunaan Linux pada perangkat lawas mengingatkan kembali bahwa software yang efisien masih sangat relevan: sistem yang lebih ringan sering memberi umur lebih panjang pada hardware terbatas. Prinsip yang sama berlaku untuk arsitektur backend. Alih-alih memecah sistem terlalu cepat menjadi banyak service, monolit modular memungkinkan batas domain yang rapi tanpa membayar biaya operasional microservices sejak awal.

Apa itu monolit modular, dan mengapa cocok untuk server lawas?

Monolit modular adalah aplikasi yang di-deploy sebagai satu unit, tetapi struktur kode dan dependensinya dipisah jelas per modul/domain. Contohnya: modul autentikasi, katalog, pesanan, pembayaran, notifikasi, dan admin berada dalam satu codebase dan satu proses aplikasi, namun memiliki boundary internal yang tegas.

Berbeda dengan monolit yang berantakan, monolit modular tetap menegakkan disiplin desain:

  • setiap modul punya API internal yang jelas,
  • akses database atau repository dibatasi,
  • dependensi antarmodul dikendalikan,
  • testing dapat dilakukan per modul,
  • refactor ke service terpisah di masa depan menjadi lebih realistis.

Pada server lawas atau VPS kecil, model ini cocok karena:

  • Lebih hemat RAM: satu proses aplikasi umumnya lebih murah dibanding banyak service dengan runtime, connection pool, cache client, dan logging masing-masing.
  • Lebih hemat CPU: komunikasi internal melalui function call lebih murah daripada network call antarlayanan.
  • Lebih sederhana dioperasikan: deployment, logging, backup, dan pemantauan lebih sedikit moving parts.
  • Lebih stabil untuk tim kecil: lebih sedikit titik gagal dibanding microservices.

Kenapa microservices sering terlalu mahal untuk traffic bertahap?

Microservices berguna bila Anda memang membutuhkan isolasi skala, otonomi tim, atau boundary operasional yang kuat. Namun pada server hemat biaya, biaya tersembunyinya sering lebih besar daripada manfaatnya.

1. Trade-off CPU

Pada monolit modular, panggilan antar domain biasanya terjadi di memori proses yang sama. Pada microservices, panggilan berubah menjadi HTTP/gRPC atau message broker. Ini menambah:

  • serialisasi/deserialisasi data,
  • overhead network stack,
  • retry dan timeout handling,
  • enkripsi transport bila menggunakan TLS,
  • latensi tambahan pada path request.

Di CPU terbatas, overhead ini terasa. Sistem bisa terlihat “modular”, tetapi throughput riil justru turun karena banyak pekerjaan non-bisnis yang harus dijalankan mesin.

2. Trade-off RAM

RAM sering menjadi bottleneck terbesar di VPS kecil. Banyak service berarti banyak hal diduplikasi:

  • runtime proses,
  • worker background per service,
  • connection pool database,
  • library logging/telemetry,
  • cache internal masing-masing service.

Monolit modular dapat berbagi satu runtime, satu pool koneksi, dan satu cache proses bila memang sesuai. Ini membuat footprint memori lebih terkendali.

3. Trade-off storage

Pada microservices, storage bukan hanya soal ukuran source code atau image container. Yang sering membengkak adalah:

  • image build per service,
  • log per container/proses,
  • artifact CI/CD,
  • schema migration yang tersebar,
  • backup data yang makin kompleks jika setiap service punya database sendiri.

Di server lawas atau disk kecil, pertumbuhan log dan artifact sering menjadi masalah operasional yang nyata.

4. Trade-off observability

Banyak tim menganggap observability bisa ditambahkan nanti. Pada microservices, ini justru wajib dari awal. Tanpa tracing, correlation ID, centralized logs, dan metrik lintas service, debugging akan cepat menjadi sulit.

Pada monolit modular, observability tetap penting, tetapi baseline-nya lebih sederhana:

  • satu alur log lebih mudah dibaca,
  • profiling lebih terpusat,
  • lebih sedikit hop yang harus ditelusuri saat terjadi error.

5. Trade-off deployment dan reliabilitas

Microservices memberi isolasi deployment, tetapi itu datang dengan harga:

  • lebih banyak pipeline CI/CD,
  • lebih banyak konfigurasi environment,
  • lebih banyak secret dan endpoint internal,
  • lebih banyak peluang ketidaksesuaian versi API.

Untuk tim kecil, kegagalan sering bukan karena kode bisnis sulit, melainkan karena koordinasi deployment, kompatibilitas kontrak API, atau service dependency yang tidak sehat.

Kapan monolit modular lebih tepat?

Memilih monolit modular untuk server lawas dan traffic bertahap masuk akal bila sebagian besar kondisi berikut terpenuhi:

  • traffic masih rendah sampai menengah dan naik secara bertahap, bukan meledak tiba-tiba,
  • tim engineering kecil, misalnya 1-5 developer,
  • aplikasi masih sering berubah di level requirement,
  • anggaran bulanan infrastruktur terbatas,
  • deployment masih dilakukan ke VPS kecil, shared hosting modern, atau satu-dua instance sederhana,
  • domain bisnis belum benar-benar stabil,
  • kebutuhan isolasi compliance atau tenant belum menuntut service terpisah.

Monolit modular juga tepat bila bottleneck utama saat ini adalah query database lambat, N+1 query, cache yang belum ada, background job yang memblokir request, atau asset statis yang belum dioptimalkan. Masalah seperti itu biasanya tidak selesai hanya dengan memecah aplikasi menjadi microservices.

Matriks keputusan: monolit modular vs microservices

KriteriaMonolit ModularMicroservices
CPU terbatasSangat cocok, overhead rendahKurang ideal, banyak overhead komunikasi
RAM kecilLebih hemat, proses lebih sedikitCepat boros karena banyak runtime dan pool
Storage kecilLebih sederhana dikelolaArtifact, image, dan log lebih banyak
Tim kecilLebih mudah dioperasikanBeban koordinasi dan DevOps lebih tinggi
Traffic bertahapUmumnya cukupSering terlalu dini
Observability matangTidak terlalu kompleks di awalWajib matang sejak awal
Deployment independen per domainTerbatasKuat bila memang dibutuhkan
Isolasi kegagalanLebih lemah karena satu proses/deployableBerpotensi lebih baik, tetapi lebih kompleks
Maintainability jangka panjangBaik jika boundary internal disiplinBaik jika tim dan operasi cukup matang
Biaya bulananBiasanya paling murahBiasanya naik lebih cepat

Jika kebutuhan utama Anda saat ini adalah efisiensi resource dan kecepatan delivery dengan tim kecil, default yang aman biasanya adalah monolit modular, bukan microservices.

Contoh skenario migrasi dari shared hosting atau VPS kecil

Skenario awal

Misalkan Anda punya aplikasi web yang awalnya berjalan di shared hosting atau VPS kecil dengan karakteristik berikut:

  • satu database relasional,
  • fitur login, produk, checkout, dan dashboard admin,
  • job email dan notifikasi masih sinkron,
  • sering terjadi timeout saat traffic promo,
  • tim hanya 2 developer.

Kesalahan umum pada tahap ini adalah langsung mendesain ulang menjadi service terpisah: auth-service, catalog-service, order-service, notification-service, dan seterusnya. Di atas kertas terlihat rapi, tetapi di mesin kecil justru memperkenalkan banyak overhead baru.

Langkah migrasi yang lebih aman

  1. Pisahkan modul di codebase, bukan proses
    Refactor menjadi modul internal: Auth, Catalog, Order, Billing, Notification.
  2. Pindahkan pekerjaan berat ke background job
    Contoh: kirim email, generate invoice, sinkronisasi stok, resize gambar.
  3. Tambahkan cache di titik yang tepat
    Jangan cache semuanya. Fokus pada query mahal atau data yang sering dibaca.
  4. Optimalkan database lebih dulu
    Indeks, paginasi, query plan, batching write, dan connection management sering memberi dampak lebih besar daripada memecah service.
  5. Gunakan reverse proxy dan kompresi
    Kurangi beban aplikasi untuk file statis, keep-alive, dan buffering.
  6. Monitoring dasar wajib ada
    Pantau CPU, RAM, disk, latency endpoint, error rate, dan slow query.

Contoh struktur modul sederhana

src/
  modules/
    auth/
      service/
      repository/
      http/
    catalog/
      service/
      repository/
      http/
    order/
      service/
      repository/
      http/
    notification/
      service/
      repository/
      jobs/
  shared/
    db/
    cache/
    logging/
    config/

Struktur seperti ini membantu tim menjaga batas domain tanpa harus memecah deployment dari hari pertama.

Contoh boundary yang sehat

Misalnya modul Order tidak langsung mengakses tabel internal modul Catalog. Ia hanya memanggil service atau repository yang memang diekspos. Jika suatu hari Catalog dipisah menjadi service sendiri, perubahan biasanya lebih terlokalisasi.

// pseudo-code
orderService.createOrder(userId, items) {
  products = catalogService.getPurchasableItems(items)
  pricing = pricingService.calculate(products)
  order = orderRepository.save(userId, pricing, items)
  notificationJob.enqueueOrderCreated(order.id)
  return order
}

Meskipun masih satu aplikasi, pola ini memaksa disiplin integrasi internal dan mengurangi coupling liar.

Dampak pada maintainability, testing, dan biaya bulanan

Maintainability

Monolit modular bisa sangat maintainable jika aturan modularitas benar-benar ditegakkan. Masalahnya bukan pada kata “monolit”, tetapi pada codebase yang tidak punya boundary. Tanda monolit mulai memburuk biasanya:

  • semua modul bebas mengakses tabel semua modul,
  • logic bisnis tersebar di controller/handler,
  • utilitas global menjadi tempat semua hal bercampur,
  • perubahan kecil memicu regresi di area yang jauh.

Jika boundary tetap rapi, monolit modular justru memudahkan refactor karena pencarian dependensi dan perubahan kontrak ada dalam satu repository dan satu siklus review.

Testing

Dari sisi testing, monolit modular punya keunggulan praktis:

  • Unit test lebih mudah dijalankan karena dependency lokal.
  • Integration test lebih sederhana karena tidak perlu menyiapkan banyak service palsu.
  • End-to-end test lebih stabil karena lebih sedikit titik network failure.

Microservices memang bisa diuji dengan baik, tetapi memerlukan disiplin kontrak API, test environment yang lebih mahal, dan sinkronisasi versi yang lebih ketat. Untuk tim kecil, overhead ini nyata.

Biaya bulanan

Tanpa mengarang angka spesifik, pola umumnya jelas:

  • monolit modular cenderung membutuhkan lebih sedikit instance/proses,
  • lebih sedikit komponen observability,
  • lebih sedikit storage untuk image dan log,
  • lebih sedikit waktu engineering untuk deployment dan troubleshooting.

Artinya, biaya bulanan bukan hanya soal tagihan server, tetapi juga biaya waktu tim. Satu insinyur yang menghabiskan banyak waktu mengurus pipeline, service discovery, tracing, dan incident lintas service adalah biaya operasional juga.

Kapan sebaiknya tetap monolit?

Tetaplah pada monolit modular jika tanda-tanda berikut masih dominan:

  • server masih mampu menangani beban setelah optimasi dasar dilakukan,
  • bottleneck utama ada di database, caching, atau query, bukan pada batas proses aplikasi,
  • tim belum punya kapasitas menjaga observability dan operasi multi-service,
  • deployment terpisah per domain belum memberi manfaat nyata,
  • sebagian besar fitur masih berbagi transaksi data yang sama,
  • failure isolation belum menjadi kebutuhan bisnis yang mendesak.

Dalam banyak kasus, menambah queue worker, cache, indeks database, dan modul boundary yang rapi memberikan ROI lebih tinggi daripada memecah arsitektur terlalu cepat.

Kapan mulai memecah service?

Memecah service mulai masuk akal bila ada alasan teknis dan operasional yang konkret, misalnya:

  • satu modul memiliki pola scaling sangat berbeda, misalnya notifikasi atau media processing jauh lebih berat daripada modul lain,
  • kebutuhan reliabilitas berbeda, misalnya kegagalan modul laporan tidak boleh mengganggu checkout,
  • tim berkembang dan ownership domain menjadi jelas,
  • siklus rilis berbeda, misalnya modul fraud atau billing perlu dirilis lebih terpisah,
  • batas data dan kontrak sudah matang,
  • dependensi eksternal atau compliance menuntut isolasi tertentu.

Penting: pecah service berdasarkan domain dan beban nyata, bukan berdasarkan daftar folder atau sekadar keinginan “mengikuti tren”.

Kandidat pertama yang sering aman dipisah

  • Worker asinkron untuk email, webhook, export, image processing.
  • Search indexing bila proses sinkronisasi dan query pencarian punya pola resource berbeda.
  • Notifikasi jika throughput dan integrasi pihak ketiganya mulai dominan.

Modul inti transaksi seperti order dan pembayaran biasanya lebih berisiko dipisah terlalu dini karena konsistensi data, retry, idempotency, dan error handling akan menjadi lebih kompleks.

Pola implementasi praktis agar monolit siap berkembang

1. Tegakkan batas modul

Buat aturan sederhana yang bisa diperiksa saat code review:

  • modul hanya boleh mengakses API internal modul lain,
  • hindari query langsung ke tabel milik domain lain jika bisa,
  • simpan DTO, event internal, atau interface di tempat yang jelas,
  • pisahkan kode shared yang benar-benar umum dari logic domain.

2. Gunakan background job untuk pekerjaan berat

Di server kecil, memindahkan pekerjaan non-kritis dari request path ke worker sering memberi penghematan resource yang besar. Pastikan job bersifat idempotent bila mungkin, dan simpan status pemrosesan agar mudah dipantau.

3. Terapkan observability minimum yang cukup

  • request log dengan correlation ID,
  • slow query log,
  • metric CPU, RAM, disk, dan latency endpoint,
  • alert untuk disk hampir penuh dan error rate naik.

Walau masih monolit, kebiasaan ini akan mempermudah jika nanti sebagian modul dipisah.

4. Kelola database dengan disiplin

Banyak monolit terasa “tidak scalable” padahal akar masalahnya ada pada akses data. Fokus pada:

  • indeks yang tepat,
  • menghindari N+1 query,
  • read/write path yang efisien,
  • batas transaksi yang jelas,
  • arsip atau pembersihan data lama bila diperlukan.

5. Siapkan kontrak internal sebelum kontrak jaringan

Sebelum membuat API antar-service, definisikan dulu kontrak internal antarmodul. Ini memaksa tim memikirkan boundary secara disiplin tanpa membayar kompleksitas distributed system terlalu awal.

Kesalahan umum yang perlu dihindari

  • Memecah service untuk menutupi codebase yang berantakan
    Jika boundary domain belum jelas di dalam satu aplikasi, memecahnya ke banyak proses biasanya hanya menyebarkan kekacauan.
  • Mengira container otomatis membuat sistem efisien
    Container membantu packaging dan deployment, tetapi tidak menghilangkan biaya runtime banyak proses.
  • Mengabaikan log dan metrics
    Server kecil lebih sensitif terhadap kebocoran memori, pertumbuhan log, dan spike disk I/O.
  • Memisahkan database terlalu cepat
    Database per service meningkatkan isolasi, tetapi juga memperumit join data, reporting, konsistensi, dan backup.
  • Mencampur shared library dan shared domain logic
    Kode umum boleh dibagi, tetapi logic domain yang terlalu dibagikan sering menciptakan coupling baru yang sulit dibongkar.

Checklist keputusan singkat

  1. Apakah aplikasi masih bisa memenuhi kebutuhan dengan satu deployment setelah optimasi database, cache, dan job queue?
  2. Apakah tim punya kapasitas untuk observability, CI/CD multi-service, dan troubleshooting distributed system?
  3. Apakah ada modul dengan kebutuhan scaling, reliabilitas, atau compliance yang benar-benar berbeda?
  4. Apakah boundary domain sudah cukup jelas untuk dipisah tanpa banyak panggilan bolak-balik?
  5. Apakah biaya operasional tambahan sepadan dengan manfaat bisnisnya sekarang?

Jika jawaban untuk sebagian besar pertanyaan di atas masih “belum”, maka monolit modular biasanya adalah keputusan yang lebih tepat.

Penutup

Untuk aplikasi yang berjalan di hardware lama, server murah, atau lingkungan traffic bertahap, monolit modular sering memberi keseimbangan terbaik antara efisiensi resource, maintainability, dan biaya operasional. Anda tetap mendapatkan struktur domain yang sehat, testing yang lebih sederhana, dan jalur migrasi yang masuk akal, tanpa langsung menanggung kompleksitas penuh microservices.

Gunakan microservices saat ada kebutuhan nyata yang membenarkannya: pola scaling berbeda, isolasi reliabilitas, ownership tim yang jelas, atau tuntutan compliance. Sampai titik itu tiba, monolit modular bukan kompromi yang buruk—justru sering menjadi pilihan engineering yang paling dewasa untuk server lawas dan traffic yang tumbuh perlahan.