Topik ini membahas arsitektur event-driven dengan pola pub/sub, mulai dari perbedaan pemanggilan langsung dengan penerbitan event, mekanisme topik dan langganan, hingga pola choreography yang membentuk alur kerja dari reaksi berantai antarlayanan. Sebuah diagram, simulasi event bus yang dapat dieksekusi, dan demo perutean event yang interaktif menyertai pembahasan.
Dari pemanggilan langsung ke event#
Pada komunikasi sinkron, layanan pesanan memanggil layanan notifikasi secara langsung ketika pesanan selesai dibuat. Pemanggilan itu mengikat kedua layanan, karena notifikasi harus hidup, alamatnya harus diketahui, dan kegagalan pemanggilan harus ditangani oleh layanan pesanan. Setiap layanan baru yang perlu mengetahui kejadian yang sama menambah satu pemanggilan pada layanan pesanan, sehingga kode penerbit berubah setiap kali daftar pendengarnya bertambah. Ketergantungan seperti ini membuat batas layanan menjadi kabur, sebab layanan pesanan ikut menangani urusan layanan lain.
Pola pub/sub membalik arah ketergantungan tersebut. Layanan pesanan tidak lagi memanggil siapa pun, tetapi menerbitkan event, yaitu catatan fakta bahwa sesuatu telah terjadi, misalnya pesanan 101 telah dibuat. Event diterbitkan ke sebuah topik, yakni saluran bernama pada event bus, dan setiap layanan yang berlangganan topik itu menerima salinannya. Layanan pesanan tidak perlu mengetahui daftar pelanggannya, dan langganan baru dapat ditambahkan tanpa mengubah kode penerbit.
Topik, langganan, dan choreography#
Diagram berikut memperlihatkan struktur dasarnya. Satu topik menerima event dari producer, dan tiga layanan berlangganan topik yang sama, sehingga satu kejadian memicu tiga pengiriman yang saling lepas. Mekanisme pengiriman satu event ke banyak langganan seperti ini disebut fan-out.
Alur kerja lintas layanan pada pola ini terbentuk tanpa koordinator terpusat. Setiap layanan bereaksi terhadap event yang diterimanya, mengerjakan bagiannya, dan bila perlu menerbitkan event lanjutan. Pola reaksi berantai ini disebut choreography (koreografi), sebab tidak ada konduktor yang mengarahkan setiap langkah. Simulasi berikut menerapkan pola tersebut, yaitu langganan layanan persediaan menerbitkan event stok_dikurangi, lalu langganan layanan pengiriman merespons event lanjutan itu.
"""Simulasi event bus pub/sub dengan koreografi antarlayanan.
Event bus menyimpan daftar langganan untuk setiap topik. Layanan yang
berlangganan dapat menerbitkan event lanjutan, sehingga alur kerja
terbentuk dari reaksi berantai tanpa koordinator terpusat.
"""
class EventBus:
"""Event bus sederhana: topik, langganan, dan publikasi event."""
def __init__(self):
self.langganan = {} # topik -> daftar fungsi penangan
def berlangganan(self, topik, penangan):
self.langganan.setdefault(topik, []).append(penangan)
def publikasikan(self, topik, event):
print(f" event '{topik}' dipublikasikan: {event}")
for penangan in self.langganan.get(topik, []):
penangan(event)
def layanan_persediaan(event):
print(f" [persediaan] stok dikurangi untuk pesanan {event['id']}")
bus.publikasikan("stok_dikurangi", {"id": event["id"]})
def layanan_notifikasi(event):
print(f" [notifikasi] email dikirim untuk pesanan {event['id']}")
def layanan_pengiriman(event):
print(f" [pengiriman] resi dibuat untuk pesanan {event['id']}")
bus.publikasikan("pengiriman_dijadwalkan", {"id": event["id"]})
bus = EventBus()
bus.berlangganan("pesanan_dibuat", layanan_persediaan)
bus.berlangganan("pesanan_dibuat", layanan_notifikasi)
bus.berlangganan("stok_dikurangi", layanan_pengiriman)
print("Layanan pesanan menerima permintaan pembuatan pesanan")
bus.publikasikan("pesanan_dibuat", {"id": 101})Layanan pesanan menerima permintaan pembuatan pesanan
event 'pesanan_dibuat' dipublikasikan: {'id': 101}
[persediaan] stok dikurangi untuk pesanan 101
event 'stok_dikurangi' dipublikasikan: {'id': 101}
[pengiriman] resi dibuat untuk pesanan 101
event 'pengiriman_dijadwalkan' dipublikasikan: {'id': 101}
[notifikasi] email dikirim untuk pesanan 101Keluaran memperlihatkan rantai yang tidak diketahui penerbit pertama. Layanan pesanan hanya menerbitkan satu event, sedangkan urutan notifikasi, persediaan, dan pengiriman muncul dari kombinasi langganan serta publikasi lanjutan. Pada simulasi ini penangan berjalan berurutan sesuai urutan langganan, sedangkan broker nyata biasanya mengirim event ke setiap langganan secara paralel, sehingga urutan reaksi antarlayanan tidak dapat diandalkan. Kebebasan inilah yang membuat layanan baru dapat disambungkan tanpa menyentuh layanan lama.
Demo perutean event#
Demo berikut menyederhanakan event bus menjadi tombol dan log. Tekan salah satu tombol publikasi, amati layanan mana saja yang bereaksi, lalu matikan salah satu langganan dan terbitkan event yang sama untuk melihat bahwa penerbit tidak berubah sedikit pun. Untuk event pesanan_dibatalkan, layanan persediaan dan pengiriman sengaja tidak berlangganan, sehingga reaksi yang muncul berbeda dari event pembuatan pesanan.
Kode sumber demo
<!DOCTYPE html>
<html lang="id">
<head>
<meta charset="utf-8">
<title>Demo perutean event</title>
<style>
body { font-family: sans-serif; font-size: 14px; margin: 12px; }
button { margin-right: 8px; padding: 6px 10px; cursor: pointer; }
label { margin-right: 12px; }
#log { margin-top: 10px; background: #f5f5f5; border: 1px solid #ddd;
height: 150px; overflow-y: auto; padding: 6px;
font-family: monospace; font-size: 12px; }
</style>
</head>
<body>
<p>
<button id="buat" type="button">Publikasikan: pesanan_dibuat</button>
<button id="batal" type="button">Publikasikan: pesanan_dibatalkan</button>
</p>
<p>
<strong>Langganan aktif:</strong>
<label><input type="checkbox" id="notif" checked> notifikasi</label>
<label><input type="checkbox" id="persediaan" checked> persediaan</label>
<label><input type="checkbox" id="pengiriman" checked> pengiriman</label>
</p>
<div id="log"></div>
<script>
var nomor = 101;
function log(teks) {
var baris = document.createElement('div');
baris.textContent = teks;
document.getElementById('log').prepend(baris);
}
function aktif(id) {
return document.getElementById(id).checked;
}
document.getElementById('buat').addEventListener('click', function () {
var id = nomor++;
log('EVENT pesanan_dibuat (pesanan-' + id + ')');
if (aktif('notif')) log(' notifikasi menerima: email dikirim');
if (aktif('persediaan')) log(' persediaan menerima: stok dikurangi, event stok_dikurangi diterbitkan');
if (aktif('persediaan') && aktif('pengiriman')) log(' pengiriman menerima: resi dibuat');
});
document.getElementById('batal').addEventListener('click', function () {
var id = nomor++;
log('EVENT pesanan_dibatalkan (pesanan-' + id + ')');
if (aktif('notif')) log(' notifikasi menerima: email pembatalan dikirim');
if (!aktif('persediaan') && !aktif('pengiriman')) log(' tidak ada langganan untuk topik ini, event diabaikan');
});
log('Siap. Klik salah satu tombol publikasi, lalu ubah langganan dan ulangi.');
</script>
</body>
</html>Konsekuensi terhadap penalaran sistem#
Kebebasan menambah langganan dibayar dengan alur yang tidak terlihat di satu tempat. Pada choreography, tidak ada satu berkas yang menggambarkan urutan lengkap dari pesanan dibuat sampai resi dibuat, karena urutan itu tersebar pada beberapa layanan. Menjawab pertanyaan sederhana seperti layanan mana saja yang dipengaruhi perubahan event memerlukan daftar langganan dari broker atau penelusuran log, sehingga banyak tim memasang pelacakan terdistribusi sebagai alat wajib.
Skema event juga menjadi kontrak yang lebih ketat daripada sebelumnya. Pelanggan mungkin belum diperbarui ketika producer menambahkan atribut baru, sehingga perubahan skema dikelola dengan aturan kompatibilitas mundur, misalnya hanya menambah atribut opsional atau menerbitkan versi topik baru. Penalaran tentang kebenaran data ikut berubah, karena setiap layanan mengelola salinannya sendiri dan memperbaruinya lewat event, sehingga salinan dapat tertinggal beberapa saat dari sumbernya. Keterlambatan semacam ini adalah konsekuensi konsistensi eventual, dan jaminan pengiriman yang menentukan kapan salinan benar-benar terbarui dibahas pada topik Semantik Pengiriman.
Kesalahan umum#
- Menulis event sebagai perintah. Event yang isinya “kirim email sekarang” mengembalikan ketergantungan yang hendak dilepaskan, sedangkan event yang benar menyatakan fakta “pesanan dibuat” dan membiarkan penangan memutuskan reaksinya. Perbaikannya adalah memeriksa kembali kata kerja pada nama event.
- Mengubah skema tanpa kompatibilitas mundur. Pelanggan lama gagal menafsirkan event baru, dan kegagalan itu muncul di layanan yang tidak disunting. Perbaikannya adalah menambah atribut secara opsional atau menerbitkan versi topik terpisah.
- Menganggap pengiriman selalu tepat satu kali. Duplikasi dapat terjadi ketika pengiriman diulang, sehingga penangan yang tidak idempoten memberikan efek ganda. Perbaikannya adalah merancang penangan yang aman terhadap pesan ganda sebagaimana dibahas pada topik Semantik Pengiriman.
Ringkasan#
- Pub/sub membalik arah ketergantungan, karena producer hanya menerbitkan event ke topik dan tidak perlu mengetahui pelanggannya.
- Choreography membentuk alur kerja dari reaksi berantai antarlayanan, sehingga layanan baru dapat disambungkan tanpa mengubah penerbit.
- Alur yang tersebar dan skema event yang menjadi kontrak menuntut pelacakan terdistribusi serta aturan perubahan skema yang kompatibel mundur.
Referensi#
- RabbitMQ Tutorials: Tutorial 3 Publish/Subscribe — RabbitMQ — Demonstrasi topik dan fan-out pada sebuah message broker.
- Apache Kafka Documentation — Apache — Konsep pub/sub terdistribusi, topik, dan semantik pengiriman pada Kafka.