Monolit modular vs microservice bukan pertanyaan gaya arsitektur semata, tetapi keputusan biaya dan risiko. Untuk produk kreatif seperti yang terinspirasi dari konteks Blåmba—proyek art-historical yang awalnya tampak kecil—kebutuhan sering berkembang tidak merata: katalog bertambah kompleks, media asset membesar, pencarian makin penting, impor data makin sering, dan publikasi punya ritme yang berbeda. Dalam kondisi seperti ini, monolit modular biasanya menjadi pilihan paling rasional sampai ada bukti kuat bahwa satu aplikasi tidak lagi cukup.

Jawaban singkatnya: pilih monolit modular jika tim masih kecil-menengah, domain masih berubah cepat, deployment ingin sederhana, dan beban operasional harus ditekan. Pertimbangkan pemisahan menjadi microservice ketika ada batas domain yang benar-benar jelas, kebutuhan skalabilitas atau reliabilitas antar-bagian sudah berbeda jauh, dan tim siap menanggung biaya observability, integrasi, CI/CD, serta operasi layanan terdistribusi.

Apa yang dimaksud monolit modular dan microservice?

Monolit modular

Monolit modular adalah satu aplikasi yang dideploy sebagai satu unit, tetapi struktur kodenya dibagi ketat per domain atau capability. Misalnya dalam satu repositori dan satu proses aplikasi, Anda punya modul:

  • Catalog untuk entitas karya, seniman, periode, provenance, relasi koleksi.
  • Media untuk penyimpanan metadata asset, transformasi gambar, turunan resolusi, checksum.
  • Search untuk indexing dan query pencarian.
  • Import untuk ingest CSV, XML, atau sumber eksternal.
  • Publishing untuk halaman publik, preview, workflow editorial.
  • Auth/Admin untuk akses backoffice dan audit trail.

Semua modul berbagi proses deploy yang sama, tetapi tidak boleh saling mengakses tabel, service, atau utilitas secara bebas tanpa kontrak yang jelas.

Microservice

Microservice memecah sistem menjadi beberapa layanan terpisah yang berkomunikasi melalui jaringan, biasanya via HTTP, gRPC, atau messaging. Misalnya service katalog, service media, dan service search masing-masing punya database, pipeline deploy, logging, monitoring, dan kadang tim pemilik sendiri.

Keuntungan utamanya adalah isolasi dan independensi. Kekurangannya: semua hal yang sebelumnya berupa pemanggilan fungsi lokal berubah menjadi masalah jaringan, kontrak API, kompatibilitas versi, retry, timeout, tracing, dan sinkronisasi data.

Mengapa produk kreatif sering lebih cocok mulai dari monolit modular?

1. Domain terlihat kecil, tetapi berubah terus

Produk kreatif dan art-historical jarang stabil di awal. Hari ini mungkin hanya butuh katalog karya dan halaman publik. Dua bulan kemudian muncul kebutuhan authority file, relasi antarobjek, timeline, multi-bahasa, impor dari spreadsheet warisan, penayangan IIIF-like image delivery, dan workflow publikasi bertahap. Jika domain masih bergerak, memecah terlalu dini akan mengunci boundary yang belum matang.

Pada monolit modular, refactor domain masih relatif murah karena perubahan kontrak antarmodul dapat dilakukan dalam satu codebase, satu review, dan satu deployment.

2. Data sering saling terkait erat

Pada proyek katalog budaya, relasi data biasanya padat: karya terkait seniman, lokasi, koleksi, event, referensi bibliografi, asset media, dan status publikasi. Jika dipisah ke banyak service terlalu cepat, satu halaman detail bisa memicu banyak panggilan jaringan atau memerlukan read model tambahan.

Monolit modular memudahkan join, transaksi, dan validasi lintas-domain selama beban sistem masih wajar. Ini penting ketika konsistensi data lebih penting daripada independensi deployment.

3. Tim kecil lebih diuntungkan oleh kesederhanaan operasi

Menjalankan satu aplikasi dengan satu pipeline deploy, satu sistem log, satu strategi backup, dan satu set alarm jauh lebih murah dibanding mengelola banyak service. Bagi tim kecil-menengah, biaya operasional ini sering lebih menentukan daripada teori skalabilitas.

4. Banyak bottleneck bisa diatasi tanpa microservice

Kebutuhan yang tumbuh tidak merata tidak selalu berarti arsitektur harus dipecah. Banyak masalah dapat diselesaikan dengan:

  • queue untuk pekerjaan berat seperti impor dan thumbnailing,
  • cache untuk halaman atau query mahal,
  • search index terpisah untuk pencarian,
  • object storage/CDN untuk media asset,
  • read replica atau optimasi query untuk beban baca tinggi.

Secara praktis, sistem bisa tetap monolit modular sambil memakai komponen infrastruktur terpisah. Ini sering memberi 80% manfaat tanpa 100% kompleksitas microservice.

Trade-off utama: monolit modular vs microservice

Biaya operasional

Monolit modular: lebih murah dijalankan. Tooling, deploy, backup, secret management, dan observability lebih sederhana.

Microservice: biaya naik cepat. Anda perlu service discovery atau konfigurasi endpoint yang rapi, distributed tracing, standard retry/timeout, versioning API, dan koordinasi insiden lintas-service.

Jika organisasi belum punya maturity operasional, microservice mudah menjadi beban permanen.

Kompleksitas deployment

Monolit modular: satu artefak deploy. Risiko utamanya adalah satu perubahan bisa memengaruhi seluruh aplikasi, tetapi proses rilis cenderung mudah dipahami.

Microservice: tiap service bisa dirilis independen, tetapi dependency antarversi menjadi tantangan. Anda harus memikirkan backward compatibility, migrasi skema data, dan urutan deployment.

Independensi deployment hanya bernilai jika memang ada kebutuhan rilis yang berbeda dan tim mampu menjaganya.

Observability dan debugging

Monolit modular: stack trace, log korelatif, dan profiling biasanya lebih mudah. Debugging flow pengguna cukup dari satu aplikasi.

Microservice: debugging membutuhkan request ID, trace propagation, log aggregation, metrik per service, dan pemahaman alur asynchronous. Tanpa observability yang baik, masalah kecil berubah menjadi investigasi panjang.

Kesalahan umum: memecah service lebih dulu lalu baru memikirkan tracing, dashboard, dan alert. Urutannya seharusnya dibalik.

Isolasi kegagalan

Monolit modular: kegagalan pada satu area bisa berdampak lebih luas jika resource bersama tidak dibatasi. Misalnya job impor yang boros CPU atau query berat di modul pencarian bisa mengganggu admin dan publikasi.

Microservice: memberi peluang isolasi kegagalan lebih baik karena workload berat bisa dipisah secara proses dan infrastruktur. Namun, isolasi tidak otomatis terjadi. Tanpa timeout, circuit breaker, rate limit, dan desain dependency yang hati-hati, kegagalan tetap dapat merambat.

Performa tim

Monolit modular: unggul untuk tim kecil hingga menengah karena onboarding lebih cepat, perubahan lintas-domain lebih langsung, dan koordinasi lebih sedikit.

Microservice: mulai unggul jika ada beberapa tim yang relatif otonom, masing-masing memiliki domain yang jelas, dan sering merilis tanpa harus menunggu bagian lain.

Jika masih satu tim backend beranggotakan sedikit orang, memecah layanan biasanya justru menurunkan throughput karena context switching dan overhead integrasi.

Maintainability jangka panjang

Monolit modular: sangat maintainable jika boundary modul dijaga tegas. Masalah muncul ketika semua modul berbagi tabel dan utilitas tanpa aturan, sehingga monolit berubah menjadi big ball of mud.

Microservice: maintainability bisa baik jika domain benar-benar stabil. Namun bila boundary salah, Anda akan memindahkan coupling dari dalam proses menjadi coupling melalui jaringan—lebih mahal dan lebih sulit diubah.

Contoh pembagian modul/domain yang realistis untuk tim kecil-menengah

Untuk konteks produk kreatif seperti Blåmba, pembagian berikut cukup realistis sebagai modular monolith tahap awal:

  • Catalog: karya, orang, institusi, event, relasi, taxonomy, authority records.
  • Media Asset: metadata file, storage key, renditions, hak akses asset, validasi file.
  • Search Indexing: sinkronisasi ke mesin pencari, schema index, boosting, faceting.
  • Import/ETL: parser sumber, mapping field, validasi, preview impor, audit hasil.
  • Publishing: draft/publish, static page model, preview, invalidasi cache.
  • Identity & Permissions: admin roles, editorial permissions, audit log.

Yang penting bukan hanya nama modul, tetapi aturan interaksinya. Misalnya:

  • Modul Publishing tidak membaca tabel mentah impor.
  • Modul Search tidak menjadi sumber kebenaran data; ia hanya read model/index.
  • Modul Media menyediakan antarmuka metadata asset, bukan memberi akses bebas ke skema penyimpanan internal.
  • Modul Import menulis melalui service domain yang valid, bukan bypass langsung ke banyak tabel produksi.

Contoh kontrak antarmodul di level aplikasi:

interface CatalogService {
  ArtworkId createArtwork(CreateArtworkCommand cmd);
  void attachMedia(ArtworkId artworkId, MediaId mediaId);
  ArtworkView getArtwork(ArtworkId artworkId);
}

interface MediaService {
  MediaId registerAsset(RegisterAssetCommand cmd);
  AssetMetadata getMetadata(MediaId mediaId);
}

interface SearchIndexer {
  void indexArtwork(ArtworkView artwork);
}

Contoh ini sederhana, tetapi ide utamanya penting: modul berbicara lewat kontrak, bukan lewat akses database langsung sesuka hati.

Pola implementasi monolit modular yang praktis

1. Pisahkan boundary di level kode dan database access

Jika semua modul boleh meng-query semua tabel, maka boundary hanya kosmetik. Minimal, terapkan aturan berikut:

  • setiap modul punya layer aplikasi/domain sendiri,
  • akses data hanya melalui repository/service modul terkait,
  • hindari utilitas bersama yang tumbuh menjadi tempat logika bisnis acak,
  • buat event internal untuk sinkronisasi longgar.

2. Gunakan asynchronous job untuk workload berat

Masalah umum pada produk kreatif adalah impor dan pemrosesan media. Jangan letakkan semuanya di request sinkron. Gunakan queue agar admin tidak menunggu proses lama dan agar resource bisa dikontrol.

// Pseudocode alur setelah artwork diperbarui
onArtworkUpdated(event) {
  enqueue("search.reindex", { artworkId: event.artworkId });
  enqueue("publishing.invalidate", { artworkId: event.artworkId });
}

worker("search.reindex", job) {
  const artwork = catalogService.getArtwork(job.artworkId);
  searchIndexer.indexArtwork(artwork);
}

Pola ini memberi sebagian manfaat decoupling tanpa memaksa Anda memecah menjadi service jaringan.

3. Pisahkan komponen infrastruktur, bukan domain secara prematur

Masuk akal bila aplikasi tetap monolit tetapi memakai:

  • database utama untuk transaksi,
  • object storage untuk asset media,
  • search engine untuk pencarian,
  • queue broker untuk job asynchronous,
  • CDN untuk distribusi aset publik.

Ini bukan microservice penuh, tetapi sering cukup untuk pertumbuhan awal hingga menengah.

4. Terapkan observability sejak masih monolit

Sebelum berpikir memecah service, pastikan Anda sudah punya:

  • structured logging dengan request/job ID,
  • metrik latency, error rate, queue depth, dan durasi job,
  • dashboard untuk endpoint dan worker penting,
  • alert untuk kegagalan impor, backlog queue, dan lonjakan error.

Jika observability pada monolit saja belum rapi, observability pada microservice hampir pasti akan lebih sulit.

Kapan pemisahan layanan mulai layak?

Microservice layak dipertimbangkan ketika ada tekanan nyata, bukan asumsi. Berikut sinyal yang cukup kuat:

1. Workload sangat berbeda dan saling mengganggu

Contoh: pemrosesan media dan impor besar menyebabkan konsumsi CPU/memori yang mengganggu API katalog dan situs publik meskipun sudah memakai queue. Jika kebutuhan scaling-nya berbeda jauh, memisahkan worker/media pipeline menjadi layanan tersendiri mulai masuk akal.

2. Domain sudah stabil dan punya batas yang jelas

Pemisahan lebih aman ketika kontrak domain sudah cukup matang. Misalnya layanan Media Asset benar-benar punya siklus hidup sendiri, storage sendiri, API jelas, dan klien dari beberapa bagian sistem.

3. Kebutuhan reliabilitas berbeda

Jika situs publik harus tetap berjalan meski sistem impor sedang bermasalah, atau pipeline media boleh tertunda tanpa memengaruhi admin katalog, pemisahan proses atau layanan bisa memberi isolasi kegagalan yang lebih baik.

4. Ritme deployment berbeda secara nyata

Bila modul tertentu berubah sangat sering atau dikelola oleh sub-tim yang berbeda, independensi deploy mulai memberi nilai bisnis dan teknis.

5. Skala organisasi menuntut ownership yang jelas

Jika satu tim tidak lagi mampu memahami seluruh sistem dan dependency antarbagian sudah stabil, pemisahan layanan bisa memperjelas ownership. Namun untuk tim kecil, argumen ini biasanya belum relevan.

Matriks keputusan: pilih yang mana?

KondisiMonolit ModularMicroservice
Tim backend 2-8 orangSangat cocokBiasanya terlalu mahal
Domain masih sering berubahSangat cocokBerisiko salah boundary
Butuh deployment sederhanaSangat cocokKurang cocok
Workload media/impor berat tapi terbatasCocok dengan queue/workerBelum perlu kecuali isolasi dibutuhkan
Reliabilitas antar domain harus terisolasi kuatTerbatasLebih cocok
Beberapa domain perlu scaling independenTerbatas, tapi bisa dibantu worker terpisahLebih cocok
Observability dan operasi masih minimLebih amanBerisiko tinggi
Boundary domain sudah matang dan stabilMasih validMulai layak dipertimbangkan

Ringkasnya: jika Anda perlu berpikir keras untuk membenarkan microservice, kemungkinan besar Anda belum membutuhkannya.

Sinyal pemicu migrasi yang sehat

Berikut indikator yang lebih konkret daripada slogan “agar scalable”:

  1. Queue backlog kronis pada pemrosesan media atau impor yang tidak terselesaikan hanya dengan menambah worker.
  2. Konflik deployment berulang karena satu area harus dirilis cepat sementara area lain butuh freeze ketat.
  3. Insiden performa terlokalisasi yang terus merusak area lain meski resource sudah dipisah secara proses.
  4. Kebutuhan penyimpanan/data lifecycle berbeda, misalnya asset media membutuhkan pipeline dan retention policy yang sangat berbeda dari katalog transaksional.
  5. Kebutuhan keamanan/akses berbeda, contohnya ingestion dari partner eksternal lebih aman jika dipisahkan dari core admin API.

Migrasi yang sehat biasanya dimulai dari satu area dengan ROI jelas, bukan membelah seluruh sistem sekaligus.

Anti-pattern: memecah service terlalu dini

1. Memecah berdasarkan tabel, bukan domain

Misalnya membuat service “artwork”, “artist”, “collection”, “event” hanya karena tabelnya terpisah. Hasilnya adalah halaman sederhana memerlukan banyak round-trip jaringan dan agregasi rumit. Pecah berdasarkan capability yang kohesif, bukan skema fisik semata.

2. Shared database antar-service

Ini menghilangkan manfaat utama microservice. Jika beberapa service tetap menulis ke tabel yang sama, coupling masih ada tetapi debugging jadi lebih sulit. Jika belum siap memisahkan data ownership, tetaplah di monolit modular dulu.

3. Sinkronisasi data terlalu sinkron

Jika setiap request publik harus memanggil 4-5 service internal berantai, latency dan failure surface akan membesar. Untuk banyak use case katalog/publikasi, lebih baik bangun read model atau indeks yang terdenormalisasi secukupnya.

4. Tidak punya strategi kontrak dan kompatibilitas

Microservice tanpa versioning, schema evolution, idempotency, dan retry policy akan rapuh. Pada domain impor dan publishing, duplicate event atau partial failure sangat umum terjadi.

5. Menganggap queue worker sebagai microservice penuh tanpa disiplin kontrak

Worker terpisah memang membantu, tetapi tetap butuh payload event yang stabil, idempotency, dan observability. Jangan hanya memindahkan kompleksitas ke background job tanpa pengendalian.

Strategi migrasi bertahap dari monolit modular

Jika suatu saat pemisahan layanan memang layak, lakukan bertahap:

  1. Rapikan boundary di monolit dulu. Pastikan modul punya API internal yang jelas.
  2. Kenalkan event/domain message untuk sinkronisasi non-kritis seperti reindexing atau invalidasi publikasi.
  3. Pisahkan proses lebih dulu, misalnya worker media berjalan terpisah dari web app, tetapi masih satu codebase bila perlu.
  4. Ekstrak satu capability dengan ROI tertinggi, biasanya media processing atau import pipeline, bukan core katalog yang relasinya kompleks.
  5. Bangun observability dan runbook sebelum traffic kritis diarahkan ke layanan baru.

Poin pentingnya: migrasi bukan tujuan, melainkan respons terhadap bottleneck spesifik.

Rekomendasi praktis untuk tim kecil-menengah

  • Mulailah dari monolit modular dengan boundary domain yang tegas.
  • Gunakan queue, worker, object storage, dan search index untuk menangani pertumbuhan yang tidak merata.
  • Jaga agar modul impor dan media tidak merusak respons API utama dengan menjalankannya asynchronous.
  • Bangun logging, metrics, tracing dasar sejak awal.
  • Ekstrak layanan hanya ketika ada beban operasional atau reliabilitas yang memang tidak lagi masuk akal ditangani dalam satu aplikasi.

Dalam banyak produk kreatif, keputusan terbaik bukan “monolit atau microservice” secara ideologis, tetapi monolit modular yang disiplin, lalu ekstraksi selektif ketika ada bukti teknis yang kuat. Itu biasanya memberi keseimbangan terbaik antara kecepatan tim, maintainability, dan biaya operasi.