Shared database vs database per service adalah keputusan arsitektur yang sering muncul saat tim mulai memecah aplikasi menjadi beberapa layanan. Untuk tim kecil hingga menengah, jawaban yang tepat jarang bersifat absolut: shared database sering lebih sederhana dan murah di awal, sementara database per service biasanya memberi batas ownership yang lebih sehat saat sistem dan tim mulai tumbuh.

Jika kebutuhan Anda masih didominasi oleh satu domain utama, perubahan skema masih terkoordinasi dengan mudah, dan jumlah engineer belum besar, shared database masih sangat masuk akal. Namun ketika banyak layanan mulai saling menulis tabel yang sama, deployment saling mengunci, dan perubahan kecil pada skema memicu regresi di banyak tempat, database per service mulai layak dipertimbangkan.

Apa yang dibandingkan?

Secara ringkas:

  • Shared database: beberapa service atau modul menggunakan satu database yang sama, sering kali bahkan mengakses tabel yang sama.
  • Database per service: setiap service memiliki database atau setidaknya schema/data store yang dikelola sendiri; service lain tidak boleh membaca atau menulis langsung ke storage tersebut.

Poin terpenting bukan sekadar jumlah database, melainkan ownership. Jika dua service bebas mengubah tabel yang sama, Anda masih memiliki coupling tingkat database meskipun layanan sudah dipisah secara proses.

Tabel perbandingan: shared database vs database per service

AspekShared DatabaseDatabase per Service
Kompleksitas awalLebih rendah; setup cepatLebih tinggi; butuh desain boundary dan integrasi antar-service
Biaya infrastrukturBiasanya lebih murah di awalNaik seiring jumlah database, backup, monitoring, dan automasi
Konsistensi transaksiMudah untuk transaksi ACID lintas modul dalam satu DBLebih sulit; sering perlu event, outbox, retry, atau kompensasi
Ownership skemaSering kabur; banyak pihak bisa mengubah tabelJelas; tiap service mengelola skema sendiri
Coupling antar layananTinggi jika service membaca/menulis tabel yang samaLebih rendah pada level data, tetapi integrasi aplikasi bertambah
DeploymentRawan saling mengganggu saat ada migrasi skemaLebih independen jika kontrak API/event stabil
ObservabilitasQuery mudah ditelusuri di satu tempat, tetapi ownership sering membingungkanBatas layanan lebih jelas, tetapi tracing lintas service lebih penting
Maintainability jangka panjangBisa menurun jika skema jadi pusat couplingBiasanya lebih sehat jika tim mampu mengelola kompleksitas operasional
Kecepatan delivery tim kecilSering lebih cepat di fase awalBisa lebih lambat di awal, lebih stabil saat skala domain/tim membesar

Kapan shared database masih masuk akal?

1. Tim masih kecil dan domain belum terpisah jelas

Jika tim berisi beberapa engineer yang bekerja pada satu backlog bersama, memisahkan database terlalu dini bisa menambah beban koordinasi. Pada tahap ini, bottleneck utama biasanya bukan isolation storage, tetapi kejelasan domain, kualitas migrasi, dan disiplin akses data.

2. Anda butuh transaksi kuat lintas modul

Misalnya pada sistem back-office atau SaaS sederhana, satu aksi pengguna perlu mengubah beberapa entitas sekaligus dan harus benar-benar atomik. Dengan shared database, transaksi seperti ini lebih mudah dijaga tanpa menambah pola seperti saga atau outbox.

3. Kapasitas operasional masih terbatas

Setiap database tambahan berarti backup tambahan, restore drill tambahan, monitoring tambahan, tuning koneksi tambahan, dan lebih banyak failure mode. Jika tim belum punya automasi dan observabilitas yang matang, menyederhanakan footprint infrastruktur sering lebih bijak.

4. Layanan belum benar-benar independen

Banyak sistem disebut microservices, padahal deployment, ownership, dan perubahan bisnisnya masih sangat terikat. Dalam kondisi seperti ini, database terpisah bisa menjadi biaya arsitektur tanpa manfaat nyata.

Catatan: Shared database bukan berarti semua orang bebas query ke tabel apa saja. Bahkan dalam satu database, Anda tetap bisa menerapkan batas melalui schema, role, view, repository, atau aturan ownership tabel.

Kapan database per service mulai layak?

1. Ownership domain sudah jelas

Contoh: service katalog mengelola produk, service order mengelola pesanan, service billing mengelola invoice. Jika masing-masing domain punya lifecycle, release cadence, dan kebutuhan skalanya sendiri, memisahkan database biasanya memberi manfaat nyata.

2. Perubahan skema sering memicu dampak lintas tim

Jika satu tim mengubah kolom atau indeks lalu tim lain ikut terdampak karena mereka query langsung ke tabel yang sama, itu sinyal coupling yang mulai mahal. Database per service memaksa integrasi lewat API atau event yang lebih eksplisit.

3. Kebutuhan performa dan scaling berbeda

Ada service yang dominan baca, ada yang dominan tulis, ada yang butuh retensi data lama, ada yang sensitif latensi. Dengan storage terpisah, Anda bisa menyesuaikan indexing, partisi, replica, atau bahkan jenis data store sesuai kebutuhan masing-masing service.

4. Tim mulai bertambah dan butuh otonomi deployment

Ketika beberapa tim mengerjakan service berbeda, basis data yang terpisah membantu mengurangi koordinasi berlebihan. Selama kontrak integrasi terkelola baik, tim bisa merilis perubahan lebih mandiri.

5. Audit, keamanan, dan boundary akses menjadi penting

Database per service memudahkan pembatasan akses. Service A tidak perlu punya kredensial ke storage service B. Ini bukan solusi ajaib untuk keamanan, tetapi surface area menjadi lebih terkendali.

Trade-off teknis yang paling sering menentukan

Konsistensi data

Shared database unggul untuk transaksi lintas entitas dalam satu commit. Sebaliknya, pada database per service Anda biasanya berhadapan dengan eventual consistency untuk alur lintas service.

Contoh e-commerce:

  • Pada shared database, pembuatan order dan pengurangan stok dapat dilakukan dalam satu transaksi.
  • Pada database per service, Order Service dan Inventory Service perlu berkoordinasi lewat API sinkron atau event asinkron. Jika salah satu gagal, Anda perlu retry, kompensasi, atau status sementara.

Ini bukan berarti database per service buruk, tetapi model error-nya berbeda. Tim harus siap menghadapi duplicate event, idempotency, dead letter queue, dan sinkronisasi status.

Ownership skema dan coupling

Shared database sering gagal bukan karena satu database itu salah, tetapi karena siapa pun bisa mengakses apa pun. Service order mulai query tabel user, service payment ikut membaca tabel cart, lalu business rule tersebar di SQL, ORM, dan job terpisah. Akibatnya perubahan kecil menjadi berisiko tinggi.

Database per service memperjelas kontrak: jika butuh data service lain, minta lewat API, event, atau replika baca yang memang dirancang untuk itu. Kekurangannya, integrasi jadi lebih banyak dan debugging bisa lebih panjang.

Kompleksitas operasional

Untuk tim kecil, ini sering menjadi faktor paling menentukan. Satu database lebih mudah dikelola daripada lima. Tetapi saat satu database menjadi hotspot untuk semua layanan, Anda akan menghadapi:

  • kontensi koneksi,
  • migrasi yang saling berbenturan,
  • query berat dari satu modul yang mengganggu modul lain,
  • sulitnya menetapkan owner untuk tuning dan cleanup data.

Database per service mengurangi gangguan silang, tetapi menambah kebutuhan automasi: provisioning, secret management, backup policy, alerting, dan tracing lintas service.

Observabilitas dan debugging

Shared database tampak lebih mudah diamati karena semua query ada di satu tempat. Namun saat banyak service mengakses tabel yang sama, membaca log database belum tentu cukup untuk menjawab pertanyaan penting: siapa pemilik data ini, siapa yang mengubahnya, dan lewat alur bisnis apa?

Pada database per service, observabilitas harus naik level. Anda butuh:

  • request tracing lintas service,
  • correlation ID di log,
  • audit event,
  • metrik retry, queue lag, dan error integrasi.

Tanpa itu, masalah konsistensi akan terasa lebih sulit daripada shared database.

Deployment dan migrasi

Pada shared database, deployment sering perlu sinkronisasi yang ketat. Migrasi skema yang memecah kolom, mengganti constraint, atau menghapus tabel bisa merusak service lain yang diam-diam bergantung pada struktur lama.

Pada database per service, migrasi lebih lokal. Namun kontrak API/event menjadi titik kritis. Anda memindahkan coupling dari level tabel ke level integrasi antarlayanan.

Praktik yang aman di kedua pendekatan:

  • gunakan migrasi yang backward-compatible terlebih dahulu,
  • hindari rename/drop mendadak pada kolom yang masih dipakai,
  • ukur query lambat sebelum dan sesudah rilis,
  • siapkan rollback atau strategi forward-fix.

Skenario praktis: e-commerce dan SaaS

Skenario e-commerce

Misalkan ada modul catalog, cart, order, payment, dan inventory.

Shared database masih cocok jika:

  • tim hanya 4-6 engineer,
  • semua modul dirilis bersama,
  • alur bisnis sering berubah cepat,
  • transaksi order dan stok ingin tetap sederhana,
  • beban belum terlalu tinggi dan bottleneck utama masih pada fitur, bukan throughput.

Database per service mulai menarik jika:

  • inventory butuh update stok dengan pola akses berbeda,
  • payment punya persyaratan audit/akses ketat,
  • catalog punya beban baca jauh lebih besar dari modul lain,
  • beberapa tim mulai memiliki area domain sendiri.

Dalam kasus ini, pemisahan tidak harus sekaligus. Sering kali domain pertama yang dipisahkan adalah yang paling jelas ownership-nya, misalnya payment atau catalog.

Skenario SaaS B2B

Misalkan ada service tenant management, billing, subscription, reporting, dan auth.

Untuk SaaS kecil, shared database sering cukup baik jika kebutuhan pelaporan dan billing masih sederhana. Tetapi ketika reporting mulai menjalankan query berat yang memengaruhi transaksi operasional, atau billing membutuhkan kontrol akses dan audit yang lebih ketat, pemisahan storage menjadi lebih masuk akal.

Pada SaaS, satu pola menengah yang sering efektif adalah:

  • database operasional utama tetap shared untuk domain yang masih rapat,
  • reporting atau analytics dipisah ke read model / warehouse,
  • service sensitif seperti billing atau auth memiliki storage dengan ownership yang lebih ketat.

Ini menunjukkan bahwa keputusan tidak harus biner penuh.

Contoh anti-pattern yang sering terjadi

1. Microservices, tetapi semua service tetap query database yang sama

Ini salah satu anti-pattern paling umum. Anda mendapat kompleksitas jaringan, deployment, dan observabilitas dari microservices, tetapi tetap menyimpan coupling paling kuat di level data. Hasilnya sering lebih buruk daripada monolith yang rapi.

2. Memisahkan database terlalu dini tanpa batas domain yang jelas

Tim kecil kadang memecah storage karena mengikuti pola arsitektur populer. Jika domain belum stabil, Anda justru akan sering bolak-balik memanggil service lain untuk data yang semula bisa dikelola sederhana.

3. Shared database tanpa aturan ownership

Satu database tidak otomatis bermasalah. Yang berbahaya adalah tidak adanya owner tabel, tidak ada review query lintas domain, dan tidak ada kebijakan migrasi. Ini membuat skema cepat menjadi "area publik" tanpa kontrol.

4. Sinkronisasi lintas service tanpa idempotency

Pada database per service, kegagalan parsial itu normal. Jika consumer event tidak idempotent, duplicate delivery bisa menyebabkan data ganda, stok negatif, atau invoice terbit dua kali.

5. Menggunakan join lintas domain sebagai kontrak bisnis

Jika satu service bergantung pada join langsung ke tabel milik domain lain, Anda menciptakan kontrak implisit yang sulit dikelola. Perubahan skema kecil pun bisa merusak alur bisnis yang tidak terlihat di level API.

Praktik implementasi yang membantu, apa pun pilihannya

Jika tetap memakai shared database

  • Tetapkan owner untuk setiap tabel atau schema.
  • Batasi akses tulis: service lain sebaiknya tidak bebas mengubah tabel domain yang bukan miliknya.
  • Gunakan migration review yang disiplin, terutama untuk perubahan destruktif.
  • Audit query berat dan indeks yang saling memengaruhi antar modul.
  • Jika memungkinkan, pisahkan akses dengan user database berbeda dan hak akses minimum.

Jika mulai menuju database per service

  • Mulai dari domain yang paling jelas ownership dan paling sering menimbulkan konflik.
  • Definisikan kontrak API/event secara eksplisit sebelum memindahkan tabel.
  • Terapkan idempotency untuk consumer dan retry yang terkontrol.
  • Gunakan pola outbox jika perlu menjembatani perubahan database dengan publikasi event secara lebih andal.
  • Pastikan tracing, log terstruktur, dan correlation ID tersedia sebelum kompleksitas integrasi meningkat.

Contoh sederhana pola outbox

Pada database per service, masalah umum adalah: data order berhasil disimpan, tetapi event untuk inventory gagal terkirim. Salah satu pendekatan yang sering dipakai adalah menyimpan perubahan bisnis dan event ke tabel outbox dalam transaksi yang sama.

BEGIN;

INSERT INTO orders (id, customer_id, status, total_amount)
VALUES ('ord_123', 'cust_9', 'PENDING', 150000);

INSERT INTO outbox_events (event_id, aggregate_type, aggregate_id, event_type, payload, status)
VALUES (
  'evt_456',
  'order',
  'ord_123',
  'OrderCreated',
  '{"order_id":"ord_123","items":[{"sku":"SKU-1","qty":2}]}' ,
  'NEW'
);

COMMIT;

Worker terpisah lalu membaca outbox_events, mengirim event ke broker, dan menandai statusnya. Ini tidak menghilangkan semua masalah distribusi, tetapi mengurangi risiko inkonsistensi antara database dan event publication.

Sinyal transisi yang perlu dipantau

Tim tidak perlu menunggu krisis besar untuk mengevaluasi ulang pendekatan arsitektur. Beberapa sinyal berikut layak dipantau:

  • Migrasi sering tertunda karena takut memengaruhi service lain.
  • Satu tabel punya terlalu banyak konsumen dari domain berbeda.
  • Release harus selalu dikoordinasikan lintas tim hanya karena perubahan skema.
  • Insiden performa dari satu modul sering menjatuhkan modul lain.
  • Debugging ownership data membingungkan: tidak jelas service mana yang menjadi source of truth.
  • Kebutuhan keamanan/audit meningkat dan akses database bersama terlalu longgar.
  • Pola scaling berbeda jauh antar domain, tetapi semua masih dipaksa berbagi resource yang sama.

Jika sebagian besar sinyal ini mulai muncul bersamaan, database per service biasanya bukan lagi sekadar preferensi arsitektur, tetapi alat untuk mengurangi friction operasional dan organisasi.

Checklist keputusan untuk tim teknis

Gunakan checklist berikut sebagai alat diskusi, bukan aturan mutlak.

  1. Apakah domain sudah punya batas yang jelas?
    Jika belum, shared database sering lebih aman daripada pemisahan prematur.
  2. Berapa besar tim dan seberapa mandiri mereka?
    Semakin banyak tim dengan area ownership sendiri, semakin berguna database per service.
  3. Seberapa sering butuh transaksi atomik lintas domain?
    Jika sangat sering dan kritis, shared database mungkin masih lebih praktis.
  4. Apakah tim siap menghadapi eventual consistency?
    Jika belum siap dengan retry, idempotency, dan observabilitas, pemisahan database bisa menyulitkan.
  5. Apakah bottleneck Anda ada di delivery atau operasional?
    Jika bottleneck utama adalah kecepatan membangun fitur, shared database mungkin cukup. Jika bottleneck adalah coupling dan konflik perubahan, database per service lebih menarik.
  6. Apakah infrastruktur dan automasi sudah memadai?
    Lebih banyak database berarti lebih banyak hal yang harus dioperasikan dengan benar.
  7. Apakah ada kebutuhan compliance, audit, atau isolasi akses?
    Domain sensitif sering menjadi kandidat pertama untuk storage terpisah.
  8. Apakah service benar-benar independen atau hanya dipisah secara proses?
    Jika tetap berbagi data secara bebas, Anda belum mendapatkan manfaat utama dari database per service.

Kesimpulan

Dalam konteks shared database vs database per service untuk tim kecil, pilihan terbaik bergantung pada beban sistem, ukuran tim, kematangan domain, dan kemampuan operasional. Shared database sering menjadi pilihan yang rasional pada fase awal karena lebih sederhana, murah, dan memudahkan transaksi kuat. Database per service mulai bernilai ketika ownership domain jelas, coupling di level data menjadi mahal, dan tim membutuhkan otonomi deployment yang lebih tinggi.

Pendekatan yang sehat bukan mengejar pola paling trendi, melainkan mengenali kapan biaya dari pendekatan saat ini mulai lebih besar daripada manfaatnya. Banyak sistem yang baik tumbuh secara bertahap: mulai dari shared database yang disiplin, lalu memisahkan storage pada domain yang memang paling membutuhkan isolation. Jika transisi dilakukan berdasarkan sinyal nyata, bukan asumsi, maintainability jangka panjang biasanya jauh lebih baik.