Chapter Event-Driven Workflows pada section orkestrasi memandang event sebagai pemicu pipeline: pesan tiba, lalu sebuah run dijalankan. Sudut pandang section ini lebih dalam — event sebagai cara komponen sistem berkomunikasi. Ketika Siti menekan tombol checkout, layanan pesanan TokoKita tidak boleh menelepon lima sistem lain satu per satu; cukup umumkan satu event, dan setiap sistem yang berkepentingan bereaksi sendiri. Pola inilah yang disebut event-driven architecture (EDA). Chapter ini membedah bentuk event yang baik, komponen arsitekturnya, dan simulasi event bus yang menoleransi pengiriman ulang.
Event sebagai Fakta Bisnis #
Event adalah fakta yang sudah terjadi — bukan perintah dan bukan permintaan. Nama jenis event yang sehat memakai bentuk lampau: order.dibuat, pembayaran.diterima, stok.berkurang. Setelah dipublikasikan, event tidak boleh diubah dan tidak menuntut balasan; penerbitnya juga tidak perlu tahu siapa saja pemakainya. Sifat inilah yang membuat sistem bisa tumbuh: layanan analitik baru cukup berlangganan tanpa menyentuh kode layanan pesanan.
Agar bisa mengalir lintas sistem, setiap event dibungkus envelope standar:
| Field | Peran |
|---|---|
id | identitas unik — kunci untuk mengenali pesan ulang |
jenis | nama fakta bisnis, misalnya order.dibuat |
waktu | waktu kejadian di sisi penerbit, bukan waktu pengiriman |
payload | data kejadian, misalnya order_id dan total |
Komponen Arsitektur Event-Driven #
Tiga peran menyusun arsitektur ini. Producer menerbitkan event begitu fakta bisnis terjadi. Event bus (atau broker) menerima, menyimpan, dan mendistribusikan event ke para peminat. Consumer berlangganan jenis event tertentu lalu bereaksi — memperbarui database, mengirim email, atau meneruskan ke pipeline data.
Pada diagram di atas, satu event order.dibuat dipakai tiga consumer untuk tiga tujuan berbeda. Layanan pesanan tidak memanggil salah satu dari ketiganya secara langsung — kopling antar layanan menempel pada event, bukan pada antarmuka masing-masing. Jika besok tim TokoKita menambah layanan loyalitas, layanan baru tersebut cukup subscribe tanpa perubahan apa pun pada producer.
Simulasi Event Bus dengan Pub/Sub #
Simulasi berikut membangun event bus dalam memori dengan struktur yang sama dengan broker produksi: envelope standar, tabel routing berbasis jenis event, dan beberapa consumer untuk satu jenis. Setiap consumer juga menyaring pesan ulang berdasarkan id.
"""Event bus mini: envelope standar (id/jenis/waktu/payload), pola
publish-subscribe, dan dedup berdasarkan id event."""
import time
import uuid
class Event:
"""Envelope standar satu event bisnis TokoKita."""
def __init__(self, jenis, payload):
self.id = uuid.uuid4()
self.jenis = jenis
self.waktu = time.time()
self.payload = payload
class EventBus:
"""Broker dalam memori: satu event diteruskan ke semua subscriber."""
def __init__(self):
self._subscriber = {} # jenis event -> daftar handler
def subscribe(self, jenis, handler):
self._subscriber.setdefault(jenis, []).append(handler)
def publish(self, event):
for handler in self._subscriber.get(event.jenis, []):
handler(event)
def handler_dengan_dedup(nama, aksi):
"""Bungkus handler dengan pemeriksaan id — pesan ulang diabaikan."""
sudah_diproses = set() # tiap consumer mencatat id sendiri
def proses(event):
if event.id in sudah_diproses:
print(f"[{nama}] duplikat {event.jenis} diabaikan "
f"(id {str(event.id)[:8]} sudah pernah tiba)")
return
sudah_diproses.add(event.id)
aksi(event)
return proses
def main():
bus = EventBus()
def kirim_email(event):
print(f"[notifikasi] email untuk pesanan {event.payload['order_id']}")
def catat_analitik(event):
print(f"[analitik] nilai pesanan {event.payload['order_id']} dicatat")
# dua subscriber berbeda untuk jenis event yang sama
bus.subscribe("order.dibuat", handler_dengan_dedup("notifikasi", kirim_email))
bus.subscribe("order.dibuat", handler_dengan_dedup("analitik", catat_analitik))
event = Event("order.dibuat", {"order_id": "ORD-03001", "total": 257_000})
print(f"Publish pertama — event {event.jenis} (id {str(event.id)[:8]}):")
bus.publish(event)
print("\nPublish ulang event yang sama (simulasi redelivery at-least-once):")
bus.publish(event)
if __name__ == "__main__":
main()Output yang Dihasilkan:
Publish pertama — event order.dibuat (id 42498098):
[notifikasi] email untuk pesanan ORD-03001
[analitik] nilai pesanan ORD-03001 dicatat
Publish ulang event yang sama (simulasi redelivery at-least-once):
[notifikasi] duplikat order.dibuat diabaikan (id 42498098 sudah pernah tiba)
[analitik] duplikat order.dibuat diabaikan (id 42498098 sudah pernah tiba)Mari kita telaah baris-baris kode di atas:
- Baris 8-15 (
class Event): envelope menyimpaniddariuuid.uuid4()— delapan digit pertamanya tercetak pada output; nilainya acak sehingga berbeda antar eksekusi. - Baris 18-29 (
class EventBus): tabel routingself._subscribermemetakan jenis event ke daftar handler;publishtinggal meneruskan ke semua terdaftar. - Baris 32-44 (
handler_dengan_dedup): setiap consumer memeliharasetid miliknya sendiri — pemeriksaan ulang tidak berbagi antar consumer, meniru consumer Kafka yang masing-masing mencatat posisi bacanya. - Baris 57-58 (dua
subscribe): dua consumer berbeda berlangganan jenis yang sama; itulah inti publish-subscribe. - Baris 62 dan 65 (publish dua kali): publish kedua memakai objek event yang sama — simulasi pesan yang dikirim ulang jaringan.
Redelivery dan Perlunya Dedup #
Mengapa pesan bisa tiba dua kali? Broker yang menjamin at-least-once lebih memilih mengirim ulang daripada kehilangan pesan: ketika ack dari consumer hilang di jaringan, broker mengirim lagi “untuk berjaga-jaga”. Akibatnya consumer yang sama bisa menerima event identik, dan hasil akhir tetap harus benar — properti idempotent yang sudah kita praktikkan pada pipeline orkestrasi.
Dedup berbasis id pada simulasi di atas adalah bentuk paling sederhana: simpan id yang pernah diproses, abaikan sisanya. Pada sistem nyata, kumpulan id disimpan di penyimpanan bertahan lama (bukan memori) agar tetap berlaku setelah consumer direstart; rincian jaminan pengirimannya kita bedah pada Chapter Delivery Semantics. Konsep EDA dan ragam pola implementasinya dirangkum secara lengkap di portal developer.confluent.io
.
Event bus dalam simulasi kita berupa kelas Python di satu proses. Produksi menuntut komponen terpisah yang bisa menahan beban, menyimpan pesan saat consumer berhenti, dan mendistribusikan ke banyak proses sekaligus — inilah pekerjaan message queue, topik Chapter Message Queues.