Event pesanan butuh tempat tinggal yang bisa dibaca banyak consumer secara independen dan diputar ulang kapan pun — itu pekerjaan broker Kafka. Sejak versi 4.0, Kafka hanya mengenal mode KRaft: ZooKeeper dihapus dan metadata cluster disimpan broker sendiri pada internal topic __cluster_metadata, dikunci quorum controller. Bagian ini menyiapkan dua hal: konfigurasi cluster KRaft dan topic tokokita.pesanan sebagai acuan kerja di cluster nyata, lalu topic yang sama pada simulasi lokal LocalKafka yang benar-benar kita jalankan dan uji di lingkungan ini. Konsep topic, partition, dan offset sudah dibedah pada chapter Topics, Partitions, dan Offsets; di sini kita memakainya.

Cluster KRaft dalam docker-compose #

Cara tercepat menyiapkan cluster pengembangan adalah image resmi apache/kafka dengan satu node merangkap broker dan controller. Berkas config/docker-compose.example.yaml memuat konfigurasi lengkapnya:

yaml
services:
  kafka:
    image: apache/kafka:4.0.0
    container_name: kafka-kraft
    ports:
      - "9092:9092"
    environment:
      KAFKA_NODE_ID: 1
      KAFKA_PROCESS_ROLES: broker,controller
      KAFKA_CONTROLLER_QUORUM_VOTERS: 1@kafka:9093
      KAFKA_LISTENERS: PLAINTEXT://:9092,CONTROLLER://:9093
      KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://localhost:9092
      KAFKA_CONTROLLER_LISTENER_NAMES: CONTROLLER
      KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: CONTROLLER:PLAINTEXT,PLAINTEXT:PLAINTEXT
      KAFKA_INTER_BROKER_LISTENER_NAME: PLAINTEXT
      KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1
      KAFKA_TRANSACTION_STATE_LOG_REPLICATION_FACTOR: 1
      KAFKA_TRANSACTION_STATE_LOG_MIN_ISR: 1
      KAFKA_GROUP_INITIAL_REBALANCE_DELAY_MS: 0

Mari kita telaah baris-baris konfigurasi di atas:

  1. Baris KAFKA_NODE_ID: identitas node dalam cluster KRaft; setiap voter quorum dirujuk dengan id ini.
  2. Baris KAFKA_PROCESS_ROLES: broker,controller: satu proses merangkap dua peran — menyimpan data (broker) sekaligus mengelola metadata (controller). Pada cluster produksi kedua peran biasanya dipisah ke node berbeda.
  3. Baris KAFKA_CONTROLLER_QUORUM_VOTERS: 1@kafka:9093: daftar anggota quorum metadata berformat id@host:port. Quorum inilah pengganti ZooKeeper; metadata tersimpan pada internal topic __cluster_metadata yang direplikasi antar controller.
  4. Baris KAFKA_LISTENERS dan KAFKA_ADVERTISED_LISTENERS: broker mendengar dua listener — PLAINTEXT di 9092 untuk klien dari luar container (diiklankan sebagai localhost:9092) dan CONTROLLER di 9093 khusus lalu lintas quorum.
  5. Baris KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1: internal topic __consumer_offsets direplikasi sekali karena cluster ini beranggota satu node; cluster produksi memakai 3 ke atas.
  6. Baris KAFKA_GROUP_INITIAL_REBALANCE_DELAY_MS: 0: menghilangkan jeda awal rebalance agar eksperimen consumer group pada Bagian 6 dan 7 tidak menunggu.

Membuat Topic #

Topic pada cluster nyata dibuat lewat skrip CLI. Perintah berikut membuat topic persis seperti yang dipakai proyek (dari config/topic.yaml):

bash
kafka-topics.sh --bootstrap-server localhost:9092 --create \
  --topic tokokita.pesanan --partitions 3 --replication-factor 1 \
  --config retention.ms=604800000 --config cleanup.policy=delete

Tiga partition dipilih agar tiga consumer bisa membaca paralel dalam satu group pada Bagian 4, sementara retensi 7 hari (604800000 ms) memberi ruang replay histori. Nilai lengkap konfigurasi topic dan klien terdokumentasi di dokumentasi Apache Kafka . Kutipan config/topic.yaml merangkum keputusan yang sama:

yaml
topic:
  nama: tokokita.pesanan
  num_partitions: 3
  replication_factor: 1
  konfigurasi:
    retention.ms: 604800000      # 7 hari (kebijakan delete)
    cleanup.policy: delete

Internal Topic dan Verifikasi Cluster #

Cluster KRaft dan consumer group menyimpan metadatanya sendiri sebagai topic internal, sehingga kafka-topics.sh --list pada cluster segar tidak pernah benar-benar kosong. Dua yang wajib dikenali: __cluster_metadata menyimpan metadata cluster (anggota quorum, daftar broker, konfigurasi) — menggantikan peran ZooKeeper sepenuhnya — dan __consumer_offsets menyimpan committed offset setiap consumer group; topic inilah yang membuat consumer group “ingat” posisinya lintas restart. Keduanya direplikasi sesuai KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR dan sebaiknya tidak diutak-atik manual.

Setelah cluster dan topic siap, tiga perintah berikut dipakai untuk memverifikasi kondisi (dijalankan pada cluster nyata, bukan di lingkungan proyek ini):

bash
kafka-topics.sh --bootstrap-server localhost:9092 --list
kafka-topics.sh --bootstrap-server localhost:9092 --describe --topic tokokita.pesanan
kafka-consumer-groups.sh --bootstrap-server localhost:9092 --list

Perintah --describe menampilkan jumlah partition, leader tiap partition, dan konfigurasi topic; kafka-consumer-groups.sh --list akan menampilkan group agregator-pesanan begitu consumer pertama dari Bagian 4 terhubung dan bergabung.

Topic pada Simulasi Lokal #

Simulasi LocalKafka menyimpan log tiap partition sebagai daftar di memori — append-only, offset monoton — sehingga perilaku topic, offset, dan consumer group bisa diuji tanpa broker. Membuat topic memakai metode create_topic:

python
from src.local_kafka import LocalKafka

broker = LocalKafka()
dibuat = broker.create_topic("tokokita.pesanan", num_partitions=3)
print(f"create_topic tokokita.pesanan (3 partition): dibuat={dibuat}")
dibuat_lagi = broker.create_topic("tokokita.pesanan", num_partitions=3)
print(f"panggilan kedua dengan partition sama     : dibuat={dibuat_lagi}")
print(f"list_topics  : {broker.list_topics()}")
print(f"describe     : {broker.describe_topic('tokokita.pesanan')}")
print(f"partitions   : {broker.partitions_for_topic('tokokita.pesanan')}")
print("statistik    :")
for stat in broker.statistik_topic("tokokita.pesanan"):
    print(f"  {stat}")

Output yang Dihasilkan:

text
create_topic tokokita.pesanan (3 partition): dibuat=True
panggilan kedua dengan partition sama     : dibuat=False
list_topics  : ['tokokita.pesanan']
describe     : {'topic': 'tokokita.pesanan', 'partitions': 3, 'total_pesan': 0}
partitions   : [0, 1, 2]
statistik    :
  {'partition': 0, 'jumlah_pesan': 0, 'offset_akhir': None}
  {'partition': 1, 'jumlah_pesan': 0, 'offset_akhir': None}
  {'partition': 2, 'jumlah_pesan': 0, 'offset_akhir': None}

create_topic bersifat idempoten: pemanggilan kedua dengan jumlah partition sama mengembalikan False tanpa galat, meniru perilaku kafka-topics.sh --create pada topic yang sudah ada. Sebaliknya, memanggil dengan jumlah partition berbeda pada topic yang sama ditolak — mengubah jumlah partition topic yang sudah berisi data melanggar pemetaan kunci yang sudah terlanjur terjadi, sehingga simulasi memilih menolaknya lebih awal.

Dengan topic tiga partition yang siap di kedua dunia — cluster KRaft dari konfigurasi di atas dan simulasi lokal yang teruji — kita lanjut ke sisi penulis stream: producer yang menerbitkan event checkout.