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:

FieldPeran
ididentitas unik — kunci untuk mengenali pesan ulang
jenisnama fakta bisnis, misalnya order.dibuat
waktuwaktu kejadian di sisi penerbit, bukan waktu pengiriman
payloaddata 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.

python
"""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:

text
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:

  1. Baris 8-15 (class Event): envelope menyimpan id dari uuid.uuid4() — delapan digit pertamanya tercetak pada output; nilainya acak sehingga berbeda antar eksekusi.
  2. Baris 18-29 (class EventBus): tabel routing self._subscriber memetakan jenis event ke daftar handler; publish tinggal meneruskan ke semua terdaftar.
  3. Baris 32-44 (handler_dengan_dedup): setiap consumer memelihara set id miliknya sendiri — pemeriksaan ulang tidak berbagi antar consumer, meniru consumer Kafka yang masing-masing mencatat posisi bacanya.
  4. Baris 57-58 (dua subscribe): dua consumer berbeda berlangganan jenis yang sama; itulah inti publish-subscribe.
  5. 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.