Ketika organisasi tumbuh, masalah utamanya biasanya bukan sekadar skala trafik, tetapi skala koordinasi. Konteks seperti pengakuan serikat pekerja Wikipedia Workers di Britain mengingatkan bahwa saat struktur organisasi, representasi tim, dan batas tanggung jawab berubah, sistem software juga sering perlu ditinjau ulang. Bukan karena tren organisasi menentukan teknologi, melainkan karena cara tim bekerja langsung memengaruhi efektivitas arsitektur.

Dalam praktiknya, pertanyaan memilih arsitektur tim jarang sesederhana “monolith vs microservices”. Opsi yang paling sering relevan justru ada tiga: monolith, modular monolith, dan services/microservices. Pilihan yang tepat bergantung pada tingkat otonomi tim, pola perubahan fitur, batas domain, biaya operasional yang mampu ditanggung, dan seberapa matang observability serta operasi on-call di organisasi Anda.

Kenapa struktur tim memengaruhi arsitektur

Arsitektur software tidak hidup di ruang hampa. Ia dipengaruhi oleh siapa yang mengubah kode, siapa yang deploy, siapa yang menanggung incident, dan bagaimana dependency antartim dikelola. Jika satu sistem dipakai oleh banyak tim, maka keputusan arsitektur akan menentukan:

  • Ownership: siapa pemilik domain, code path, dan operasionalnya.
  • Dependency antartim: apakah perubahan kecil perlu koordinasi lintas tim.
  • Kecepatan rilis: apakah tim bisa deploy sendiri atau menunggu batch release bersama.
  • Biaya operasional: seberapa banyak pipeline, infrastruktur, monitoring, dan environment yang harus dipelihara.
  • Observability dan debugging: seberapa mudah melacak request, error, dan bottleneck.
  • On-call: apakah incident terlokalisasi atau justru tersebar ke banyak service.
  • Maintainability: seberapa mudah sistem dipahami, diuji, dan direfaktor.

Masalahnya, banyak organisasi terlalu cepat menganggap pertumbuhan tim otomatis berarti harus berpindah ke microservices. Padahal sering kali yang dibutuhkan adalah batas modul yang lebih tegas, bukan distribusi jaringan.

Opsi arsitektur: kapan cocok, kapan menyulitkan

1. Monolith: sederhana secara operasional, cepat untuk tim yang masih rapat

Monolith adalah aplikasi tunggal dengan satu deployment unit utama. Ia bisa sangat efektif jika domain masih relatif terhubung, tim belum terlalu terfragmentasi, dan kebutuhan operasional ingin dijaga tetap sederhana.

Kelebihan utama monolith:

  • Satu repository atau satu unit deploy memudahkan setup awal.
  • Debugging lebih mudah karena alur eksekusi lokal tidak melewati jaringan.
  • Transaksi database lebih sederhana ketika banyak operasi harus konsisten.
  • Testing integrasi sering lebih murah daripada pada sistem terdistribusi.
  • Observability dasar lebih mudah dibangun karena log dan trace tidak menyebar ke banyak service.

Kekurangannya:

  • Ownership mudah kabur: semua orang bisa menyentuh semua area.
  • Build dan test dapat melambat seiring pertumbuhan kode.
  • Release bisa menjadi event koordinasi besar jika tidak ada isolasi modul.
  • Perubahan kecil di satu domain berisiko memengaruhi domain lain.
  • Skalabilitas organisasi lebih sulit jika banyak tim bekerja di codebase yang sama tanpa aturan kuat.

Monolith cocok bila masalah dominan Anda adalah kecepatan membangun produk, bukan kemandirian banyak tim. Namun monolith yang tidak disiplin mudah berubah menjadi big ball of mud: semua domain tercampur, dependency tidak jelas, dan test menjadi rapuh.

2. Modular monolith: kompromi praktis yang sering paling masuk akal

Modular monolith mempertahankan satu deployment unit utama, tetapi dengan batas modul yang eksplisit. Setiap modul memiliki API internal, model domain, dan aturan dependency yang jelas. Untuk organisasi yang sedang tumbuh, ini sering menjadi langkah terbaik karena memberikan kejelasan ownership tanpa biaya penuh sistem terdistribusi.

Kelebihan modular monolith:

  • Batas domain lebih jelas daripada monolith biasa.
  • Koordinasi antartim berkurang karena kontrak internal lebih tegas.
  • Masih murah secara operasional dibanding microservices.
  • Lebih mudah diobservasi dan diuji dibanding arsitektur terdistribusi.
  • Menjadi jalur migrasi yang baik jika suatu hari beberapa modul memang perlu dipisah.

Kekurangannya:

  • Butuh disiplin arsitektur; tanpa enforcement, modul hanya ada di diagram.
  • Satu deployment tetap berarti beberapa perubahan mungkin masih terkunci dalam release yang sama.
  • Isolasi performa dan kegagalan tidak sebaik service terpisah.
  • Tim yang sangat otonom kadang tetap merasa terhambat oleh shared runtime atau shared database.

Jika organisasi Anda mulai membentuk tim per domain, tetapi belum siap menanggung biaya observability, deployment, security, dan on-call dari puluhan service, modular monolith biasanya memberi hasil terbaik.

3. Services/Microservices: otonomi tinggi, tetapi biaya koordinasi dan operasi juga tinggi

Arsitektur service memecah sistem menjadi deployment unit yang berdiri sendiri. Ini memungkinkan tim memiliki codebase, pipeline, dan lifecycle rilis masing-masing. Namun imbalannya adalah kompleksitas distribusi: komunikasi jaringan, versioning API, retry, idempotency, tracing, dan kegagalan parsial.

Kelebihan utama services:

  • Tim bisa deploy mandiri dengan lebih sedikit konflik jadwal rilis.
  • Ownership domain lebih tegas.
  • Skala performa per service bisa dioptimalkan sesuai kebutuhan.
  • Blast radius dapat diperkecil jika isolasi benar-benar diterapkan.

Kekurangan utama services:

  • Biaya operasional meningkat tajam: CI/CD, secrets, monitoring, alerting, logging, tracing, runtime, dan network policy.
  • Debugging lebih sulit karena masalah menyebar lintas service.
  • Data consistency menjadi lebih rumit; transaksi lintas service tidak lagi gratis.
  • On-call sering memburuk jika ownership dan runbook belum matang.
  • Latency dan failure mode bertambah karena komunikasi melalui jaringan.

Microservices bekerja baik jika organisasi memang sudah memiliki batas domain yang stabil, tim platform atau minimal praktik platform yang memadai, dan kebutuhan otonomi tim benar-benar lebih besar daripada biaya operasinya.

Dampak langsung ke ownership, rilis, observability, dan on-call

Ownership

Dalam monolith tanpa modularisasi, ownership sering menjadi “milik semua orang”, yang terdengar fleksibel tetapi berujung pada area abu-abu. Bug production bisa berpindah tangan karena tidak jelas siapa pemilik domain tertentu. Pada modular monolith, ownership bisa ditetapkan per modul. Pada services, ownership biasanya paling jelas, tetapi hanya jika kontrak service, dokumentasi, dan tanggung jawab operasionalnya juga jelas.

Ownership yang baik biasanya mencakup tiga hal:

  • Pemilik perubahan kode.
  • Pemilik metrik dan error budget.
  • Pemilik incident response dan perbaikan pascainsiden.

Dependency antartim

Microservices tidak otomatis menghapus dependency antartim; sering justru mengubah dependency kode menjadi dependency API dan proses. Jika satu tim butuh field baru dari service tim lain, mereka tetap harus menunggu kontrak, implementasi, dan rollout. Monolith atau modular monolith kadang lebih cepat untuk alur ini, karena perubahan lintas modul masih bisa dilakukan dalam satu branch dan satu review lintas domain.

Pertanyaan yang lebih tepat bukan “berapa banyak service?”, tetapi berapa banyak dependency sinkron yang dibutuhkan untuk menyelesaikan satu perubahan bisnis?

Kecepatan rilis

Jika bottleneck Anda adalah konflik branch, antrian code review, atau pipeline test yang panjang, microservices mungkin membantu hanya bila masalahnya memang berasal dari coupling domain. Jika bottleneck berasal dari governance, approval manual, atau testing yang tidak stabil, memecah service belum tentu mempercepat rilis.

Modular monolith sering memberi peningkatan signifikan dengan cara:

  • Membatasi dependency antar modul.
  • Memisahkan test suite per modul.
  • Menetapkan rule ownership per area.
  • Mendorong contract testing internal.

Biaya operasional

Setiap service baru menambah biaya yang tidak terlihat di awal:

  • Pipeline build dan deploy.
  • Repositori, secrets, dan konfigurasi environment.
  • Dashboard, alert, dan log retention.
  • Kebijakan rollback dan disaster recovery.
  • Patch keamanan, dependency update, dan sertifikat.

Tim sering meremehkan biaya ini. Secara organisasi, sepuluh service kecil belum tentu lebih murah dikelola daripada satu modular monolith yang sehat.

Observability dan debugging

Dalam monolith, stack trace dan log sering cukup untuk menemukan akar masalah. Dalam services, Anda perlu correlation ID, distributed tracing, structured logging, dan metrik per dependency. Tanpa itu, incident akan berubah menjadi pencarian log lintas sistem yang lambat dan melelahkan.

Minimal, arsitektur service perlu standar observability seperti:

  • Request ID yang diteruskan lintas hop.
  • Structured log dengan field konsisten.
  • Latency, error rate, dan saturation per service.
  • Trace untuk request penting atau alur bisnis kritis.

On-call

Pada monolith, on-call bisa berat karena blast radius besar. Namun pada microservices, on-call bisa lebih buruk bila tanggung jawab terlalu tersebar dan dependensi service saling berantai. Incident sederhana dapat memicu war room antartim karena gejalanya muncul di banyak tempat sekaligus.

Jika organisasi belum punya rotasi on-call yang jelas, runbook, ownership metrik, dan batas service yang stabil, memecah sistem terlalu cepat sering hanya memecah rasa sakit menjadi unit lebih kecil.

Framework keputusan: pilih berdasarkan masalah yang nyata

Gunakan pertanyaan berikut untuk mengevaluasi apakah Anda sebaiknya tetap dengan monolith, beralih ke modular monolith, atau memecah menjadi services.

Pertanyaan inti

  1. Apakah domain bisnis sudah punya batas yang cukup stabil?
    Jika batas domain masih berubah cepat, memecah menjadi service permanen bisa prematur.
  2. Apakah tim perlu deploy benar-benar mandiri?
    Jika sebagian besar fitur tetap membutuhkan koordinasi erat, service terpisah belum tentu membantu.
  3. Apakah bottleneck utama ada pada coupling kode atau pada proses organisasi?
    Masalah approval, environment, atau test flakiness tidak selesai hanya dengan microservices.
  4. Apakah platform dan observability sudah siap?
    Tanpa standar logging, tracing, secrets, CI/CD, dan runtime, biaya operasi akan melonjak.
  5. Apakah model data bisa dipisah secara masuk akal?
    Jika banyak transaksi lintas domain harus konsisten real-time, shared database monolith mungkin masih lebih tepat.
  6. Apakah tim siap menanggung on-call per service?
    Service tanpa ownership operasional hanya memindahkan beban, bukan menyelesaikannya.

Aturan praktis yang sering berguna

  • Pilih monolith jika tim masih kecil sampai menengah, domain belum stabil, dan prioritas utama adalah kesederhanaan delivery.
  • Pilih modular monolith jika organisasi mulai membentuk tim per domain dan butuh batas ownership yang jelas, tetapi belum siap menanggung kompleksitas sistem terdistribusi.
  • Pilih services jika domain sudah matang, kebutuhan deploy independen tinggi, jalur skalabilitas berbeda per domain, dan praktik platform/observability sudah cukup dewasa.

Sinyal kapan arsitektur perlu dipecah atau justru disederhanakan

Sinyal bahwa monolith perlu dimodularisasi atau sebagian dipisah

  • Perubahan di satu area sering merusak area lain yang tidak terkait.
  • Satu pipeline release menjadi bottleneck utama banyak tim.
  • Ownership domain tidak jelas dan incident sering dilempar antar tim.
  • Bagian tertentu punya kebutuhan skala, reliabilitas, atau lifecycle yang sangat berbeda.
  • Tim membutuhkan ritme rilis berbeda dan dapat dibatasi dengan kontrak domain yang jelas.

Sinyal bahwa microservices Anda terlalu cepat atau terlalu jauh

  • Banyak service tipis yang hanya meneruskan request tanpa logika domain berarti.
  • Satu fitur sederhana butuh perubahan sinkron di banyak service.
  • Incident sulit ditelusuri karena tracing dan log tidak konsisten.
  • Tim menghabiskan lebih banyak waktu mengelola infra daripada mengembangkan produk.
  • Skema data lintas service saling bocor melalui akses langsung atau kontrak API yang rapuh.
  • On-call dipenuhi alert noise dari dependency internal.

Sinyal bahwa penyederhanaan lebih masuk akal

Jika beberapa service selalu dirilis bersama, dimiliki oleh tim yang sama, memakai data yang sangat erat, dan tidak punya kebutuhan skalabilitas berbeda, kemungkinan itu kandidat untuk digabung kembali atau minimal dikonsolidasikan ke modular monolith.

Arsitektur yang baik bukan yang paling terdistribusi, melainkan yang paling cocok dengan bentuk kerja tim dan biaya operasional yang mampu ditanggung organisasi.

Contoh matriks evaluasi arsitektur

Matriks berikut bisa dipakai dalam diskusi engineering leadership atau architecture review. Nilai 1 berarti lemah/tidak cocok, nilai 5 berarti kuat/sangat cocok untuk kebutuhan organisasi Anda.

Kriteria                     | Monolith | Modular Monolith | Services/Microservices
----------------------------|----------|------------------|------------------------
Kesederhanaan operasional   | 5        | 4                | 2
Kejelasan ownership         | 2        | 4                | 5
Deploy independen per tim   | 1        | 2                | 5
Kemudahan debugging         | 5        | 4                | 2
Biaya observability         | 5        | 4                | 2
Isolasi failure             | 2        | 3                | 4
Konsistensi data            | 5        | 4                | 2
Skala organisasi            | 2        | 4                | 5
Kemudahan refactor domain   | 2        | 4                | 3
Beban on-call               | 4        | 3                | 2

Gunakan matriks ini sebagai titik awal, bukan jawaban final. Bobot tiap kriteria harus disesuaikan. Misalnya, jika organisasi Anda sangat sensitif terhadap biaya operasional dan belum punya tim platform, bobot “kesederhanaan operasional” dan “biaya observability” sebaiknya lebih tinggi.

Contoh penilaian berbobot

Misalkan kondisi Anda seperti ini:

  • Tiga sampai lima tim backend.
  • Domain mulai terbagi, tetapi masih sering berubah.
  • Belum ada platform team khusus.
  • Incident sedang meningkat karena ownership kabur.
  • Release melambat akibat codebase yang makin besar.

Dalam situasi tersebut, modular monolith sering menjadi langkah transisi terbaik: cukup kuat untuk memperjelas batas tim, tetapi belum memaksa organisasi membayar seluruh biaya microservices.

Anti-pattern umum saat memilih arsitektur tim

1. Memecah berdasarkan layer teknis, bukan domain

Contoh buruk: membuat service terpisah untuk “user-service”, “validation-service”, atau “logging-service” tanpa batas domain bisnis yang jelas. Hasilnya adalah dependency lintas service yang tinggi dan alur request yang bertele-tele.

Lebih baik pecah berdasarkan kapabilitas domain yang punya lifecycle dan ownership berbeda.

2. Shared database pada microservices tanpa batas yang disiplin

Ini anti-pattern klasik. Service tampak terpisah di diagram, tetapi masih saling membaca tabel database secara langsung. Akibatnya:

  • Kontrak sebenarnya tidak ada pada API, tetapi tersembunyi di skema data.
  • Perubahan schema menjadi berisiko lintas tim.
  • Ownership domain kembali kabur.

Jika belum siap memisahkan data, modular monolith biasanya lebih jujur dan lebih aman.

3. Menganggap microservices menyelesaikan masalah organisasi

Jika masalah Anda adalah review lambat, prioritas produk yang berubah-ubah, atau ownership yang tidak jelas, memecah service hanya akan memperbesar permukaan masalah. Arsitektur bisa membantu, tetapi tidak menggantikan kejelasan proses dan tanggung jawab.

4. Modul hanya ada di folder, bukan di boundary

Pada modular monolith, memisahkan direktori saja tidak cukup. Anda perlu aturan dependency yang jelas. Modul A tidak boleh mengakses detail internal modul B sembarangan. Jika semua modul bebas saling impor atau memakai tabel yang sama tanpa kontrak, Anda kembali ke monolith biasa yang kusut.

5. Terlalu cepat menambah asynchronous messaging

Event-driven communication berguna, tetapi menambah kompleksitas: ordering, idempotency, duplicate delivery, retry, dan observability. Jangan menambah broker hanya karena ingin terlihat loosely coupled. Gunakan jika memang ada kebutuhan bisnis atau teknis yang jelas.

Panduan implementasi praktis: dari monolith ke modular monolith, lalu ke services bila perlu

Langkah 1: petakan domain dan alur perubahan

Mulailah dari data nyata:

  • Area mana yang paling sering berubah?
  • Perubahan mana yang paling sering melibatkan lebih dari satu tim?
  • Incident paling banyak berasal dari domain mana?
  • Bagian mana yang punya kebutuhan skala dan SLA berbeda?

Pemetaan ini lebih berguna daripada memulai dari diagram service kosong.

Langkah 2: buat boundary di tingkat kode

Dalam modular monolith, tujuan utamanya adalah mencegah akses lintas domain yang sembarangan. Anda bisa menerapkan boundary dengan:

  • Struktur paket/modul yang jelas.
  • Interface internal per modul.
  • Larangan akses langsung ke model atau repository modul lain.
  • Test arsitektur untuk memverifikasi dependency.

Contoh pseudo-struktur:

/src
  /billing
    api/
    domain/
    application/
    infrastructure/
  /membership
    api/
    domain/
    application/
    infrastructure/
  /search
    api/
    domain/
    application/
    infrastructure/

Intinya bukan nama foldernya, tetapi aturan bahwa modul lain hanya berinteraksi melalui lapisan yang memang diekspos.

Langkah 3: standarkan observability sebelum distribusi

Sebelum memecah service, pastikan minimal Anda sudah punya:

  • Structured logging.
  • Error reporting yang konsisten.
  • Metrik request, dependency, queue, dan database.
  • Request/correlation ID.
  • Dashboard dan alert berbasis SLO atau sinyal yang relevan.

Jika observability masih lemah dalam satu aplikasi, memecah menjadi banyak service biasanya hanya memperburuk visibilitas.

Langkah 4: pisahkan service dari domain yang benar-benar layak

Kandidat service yang baik biasanya memiliki sebagian besar ciri berikut:

  • Batas domain jelas.
  • Traffic atau resource profile berbeda signifikan.
  • Lifecycle rilis berbeda dari sistem utama.
  • Tim pemilik jelas dan siap on-call.
  • Kontrak data dan API bisa distabilkan.

Contoh yang lebih masuk akal sering berupa domain seperti indexing/search, media processing, atau billing integration tertentu, bukan sekadar memecah CRUD dasar tanpa kebutuhan khusus.

Langkah 5: gunakan kontrak dan kompatibilitas mundur

Begitu service dipisah, disiplin kontrak menjadi wajib. Praktik yang umum dan aman antara lain:

  • Tambahkan field API secara kompatibel, jangan langsung menghapus yang lama.
  • Gunakan timeout yang masuk akal pada panggilan jaringan.
  • Terapkan retry secara selektif, bukan membabi buta.
  • Pastikan operasi penting bersifat idempotent jika memungkinkan.

Ini penting karena jaringan selalu menambah kemungkinan partial failure.

Contoh checklist keputusan arsitektur untuk rapat teknis

Checklist ini dapat dipakai saat mengevaluasi apakah sebuah domain perlu tetap di monolith, dimodularisasi, atau dipisah menjadi service.

[ ] Batas domain sudah dipahami dan relatif stabil
[ ] Tim pemilik domain sudah jelas
[ ] Perubahan lintas domain bisa diidentifikasi dari histori PR/incident
[ ] Kebutuhan deploy independen benar-benar ada
[ ] Biaya observability tambahan sudah dihitung
[ ] On-call ownership dan runbook sudah tersedia
[ ] Model data tidak bergantung pada transaksi ACID lintas domain yang sulit dipisah
[ ] SLO/latency target memang membutuhkan isolasi tersendiri
[ ] Ada rencana migrasi dan rollback yang realistis
[ ] Kesuksesan perubahan bisa diukur setelah implementasi

Jika banyak kotak belum tercentang, biasanya terlalu dini untuk memecah menjadi service.

Kesimpulan

Memilih arsitektur tim saat organisasi tumbuh bukan soal mengikuti tren, tetapi soal menyesuaikan boundary software dengan boundary kerja tim. Monolith unggul dalam kesederhanaan. Modular monolith sering menjadi pilihan paling praktis ketika tim mulai membesar dan ownership perlu diperjelas. Services atau microservices cocok ketika domain sudah matang, kebutuhan deploy independen tinggi, dan organisasi siap menanggung biaya observability, operasi, dan on-call.

Jika Anda ragu, default yang aman biasanya bukan langsung memecah sistem, melainkan memperjelas modul, kontrak, dan ownership lebih dulu. Setelah itu, pisahkan hanya domain yang benar-benar menunjukkan kebutuhan teknis dan organisasional yang nyata. Dengan pendekatan ini, arsitektur tetap melayani tim, bukan sebaliknya.