Dalam transformasi arsitektur dari monolit menuju microservices, pola komunikasi asinkron berbasis pesan (event-driven architecture) menjadi tulang punggung pertukaran data antar-layanan. Pengembang memanfaatkan perantara pesan (message broker) berkinerja tinggi seperti Apache Kafka, RabbitMQ, atau AWS SQS agar beban kerja dapat didistribusikan secara elastis tanpa memblokir siklus permintaan HTTP pengguna.
Namun, godaan menyederhanakan alur penulisan data sering kali melahirkan antipola klasik yang sangat berbahaya: dual write (penulisan ganda). Di dalam fungsi pengendali aplikasi, pengembang menulis transaksi ke basis data lokal (misalnya orders), lalu langsung memanggil klien broker untuk menyiarkan event (broker.publish("order_created", payload)). Praktik naif ini mengasumsikan bahwa dua operasi jaringan independen tersebut akan selalu berhasil atau selalu gagal secara serentak—sebuah ilusi fatal yang merusak fondasi integritas sistem terdistribusi.
Anatomi Kegagalan Dual Write dan Bencana Split-Brain
Masalah mendasar dari dual write adalah ketiadaan transaksi atomik terpadu yang mampu mengikat basis data relasional dan perantara pesan secara bersamaan:
- Skenario Kegagalan Broker Setelah Komit Basis Data: Bayangkan skenario ketika pesanan belanja pelanggan berhasil disimpan ke tabel basis data lokal, namun beberapa milidetik kemudian sambungan jaringan ke cluster Kafka mengalami gangguan sekejap (timeout atau pemutusan koneksi TCP). Transaksi basis data telah terlanjur dikomit, saldo pengguna telah terpotong, tetapi antrean pesan tidak pernah menerima event pembuatan pesanan. Akibatnya, layanan gudang (inventory service) tidak pernah menyiapkan barang dan layanan notifikasi tidak mengirim email tanda terima. Sistem terjerumus ke kondisi asimetris yang menuntut intervensi manual tim operasional.
- Skenario Transaksi Dibatalkan Setelah Event Telah Mengudara: Sebaliknya, jika pengembang membalik urutan dengan menyiarkan pesan terlebih dahulu ke broker baru kemudian mengeksekusi komit basis data, bencana yang lebih parah mengintai. Jika koneksi basis data tiba-tiba menabrak kendala unique constraint violation atau kegagalan transaksi disk, perubahan lokal dibatalkan (rollback). Akan tetapi, pesan ke broker telah terlanjur disiarkan dan diserap oleh puluhan microservices konsumen. Layanan hilir akan mulai memproses transaksi siluman yang sebenarnya tidak pernah sah di basis data utama.
- Kegagalan Protokol Two-Phase Commit (2PC): Upaya mengikat kedua belah pihak menggunakan transaksi terdistribusi berbasis 2PC (Two-Phase Commit / XA) bukan solusi yang layak dalam arsitektur modern. Protokol 2PC memperkenalkan latensi kunci yang masif, mematikan ketersediaan sistem (availability), dan tidak didukung secara native oleh sebagian besar message broker modern berkategori throughput tinggi.
Menegakkan Jaminan Pengiriman dengan Pola Transactional Outbox
Membangun konsistensi data yang tangguh tanpa mengorbankan performa memerlukan pemisahan tanggung jawab penulisan pesan melalui Transactional Outbox Pattern:
- Jadikan Tabel Outbox Sebagai Bagian Transaksi Lokal ACID: Buat tabel khusus bernama
outbox_eventsdi dalam basis data layanan yang sama dengan entitas bisnis Anda. Setiap kali operasi bisnis berlangsung (misalnya menyimpan order baru), masukkan muatan pesan event ke dalam tabel outbox tersebut di dalam satu blok transaksi database lokal yang sama:
BEGIN TRANSACTION;
INSERT INTO orders (id, user_id, total, status) VALUES ('ord_102', 'usr_88', 750000, 'PAID');
INSERT INTO outbox_events (id, aggregate_type, aggregate_id, payload, created_at)
VALUES ('evt_901', 'Order', 'ord_102', '{"orderId":"ord_102","total":750000}', NOW());
COMMIT;Dengan pendekatan ini, event dijamin 100% tersimpan jika dan hanya jika data bisnis berhasil dikomit ke penyimpanan permanen.
2. Manfaatkan Mekanisme Change Data Capture (CDC): Alih-alih membuat proses polling berkala yang membebani CPU database, pasang modul CDC tingkat enterprise seperti Debezium yang membaca langsung catatan transaksi mentah (write-ahead log / WAL pada PostgreSQL atau Binlog pada MySQL). Modul CDC akan menyerap setiap penambahan baris baru di tabel outbox secara streaming dan meneruskannya ke Kafka dengan jaminan pengiriman at-least-once.
3. Wajibkan Idempoten pada Sisi Konsumen (Consumer Idempotency): Mengingat protokol pengiriman jaringan selalu menghadirkan potensi duplikasi pesan (at-least-once delivery), setiap layanan konsumen wajib mengimplementasikan tabel pelacak ID pesan atau kueri kondisional idempoten. Konsumen harus memverifikasi apakah evt_901 telah pernah diproses sebelumnya sebelum memicu efek samping bisnis.
Keandalan sistem backend terdistribusi tidak dibangun di atas asumsi bahwa jaringan akan selalu lancar, melainkan di atas arsitektur matang yang siap mengantisipasi setiap titik kegagalan dengan jaminan matematis yang tak terbantahkan.
