Bayangkan kamu punya tiga layanan terpisah: satu menerima pesanan, satu mengirim notifikasi, satu lagi menyusun laporan harian. Selama masih satu proses, semuanya gampang — tinggal panggil fungsi. Begitu kamu pisah ke server berbeda, kamu mulai menempel HTTP call di sana-sini. Pesanan masuk, panggil notifikasi, panggil laporan. Kalau notifikasi sedang down, pesanan ikut gagal. Jaringannya tumbuh jadi gado-gado yang sulit dirawat, dan kamu tidak lagi tahu siapa memanggil siapa. Kafka menyelesaikan ini dengan satu log terpusat: setiap layanan cukup menulis dan membaca pesan, tanpa saling kenal alamat satu sama lain. Mari bangun sendiri, dari broker sampai consumer.
Kenapa Kafka, Bukan Sekadar Message Queue
Kalau kebutuhanmu cuma antrian tugas sekali jalan, Celery dengan RabbitMQ atau Redis sudah cukup dan jauh lebih ringan — saya pernah bahas di tutorial Celery. Kafka baru terasa manfaatnya ketika kamu butuh aliran kejadian yang bisa diputar ulang, dikonsumsi banyak tim sekaligus, dan menahan volume besar. Broker Kafka menyimpan pesan di disk, bukan di RAM, jadi pesan tidak hilang saat consumer mati. Kamu juga bisa membaca ulang log dari offset tertentu kapan pun kamu mau.
Perbedaan paling krusial: di message queue biasa, pesan hilang begitu dibaca. Di Kafka, pesan tetap tersimpan sampai masa retensi habis (default 7 hari). Itulah kenapa Kafka disebut event log, bukan sekadar antrian.
Konsep Dasar: Topic, Partition, dan Consumer Group
Tiga istilah ini wajib nyantol dulu sebelum ngoding. Topic adalah kategori pesan, ibarat folder — misalnya orders atau payments. Partition adalah potongan sebuah topic yang membuat data tersebar ke beberapa bagian agar bisa diproses paralel. Consumer group adalah sekumpulan consumer yang bekerja sama; tiap pesan dalam satu topic hanya diproses oleh satu anggota group, tapi group lain tetap bisa membaca pesan yang sama untuk keperluan berbeda. Kombinasi ketiganya membuat Kafka bisa menambah pembaca dan penulis tanpa mengubah arsitektur.
Menyiapkan Broker Kafka dengan Docker Compose
Kita pakai image resmi apache/kafka yang sejak versi 3.3 memakai mode KRaft — tidak butuh ZooKeeper lagi. Kalau Docker masih asing, mampir dulu ke Belajar Docker untuk Pemula. Simpan berkas berikut sebagai docker-compose.yml:
services:
broker:
image: apache/kafka:3.9.0
container_name: kafka-broker
ports:
- "9092:9092"
environment:
KAFKA_NODE_ID: 1
KAFKA_PROCESS_ROLES: broker,controller
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_CONTROLLER_QUORUM_VOTERS: 1@localhost:9093
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
KAFKA_NUM_PARTITIONS: 3
Jalankan docker compose up -d, lalu pantau lognya dengan docker compose logs -f broker. Tunggu sampai log menampilkan baris "Kafka Server started" — itu tanda broker siap menerima koneksi. Kalau kamu lihat "kafka-broker" sudah berstatus running lewat docker ps, berarti tinggal selangkah lagi.
Port 9092 adalah tempat client (producer/consumer) terhubung, sedangkan 9093 dipakai broker untuk komunikasi internal controller. Jangan sampai tertukar saat menulis kode.
Install Library confluent-kafka
Ada beberapa pilihan library, tapi confluent-kafka adalah yang paling dekat dengan perilaku asli Kafka karena membungkus librdkafka (C). Instal lewat pip:
pip install confluent-kafka
Menulis Producer Pertama
Producer bertugas mengirim pesan ke sebuah topic. Buat file producer.py dengan isi berikut:
import json
from confluent_kafka import Producer
producer = Producer({"bootstrap.servers": "localhost:9092"})
def delivery_report(err, msg):
if err is not None:
print(f"Gagal kirim: {err}")
else:
print(f"Terkirim ke {msg.topic()} [{msg.partition()}] offset {msg.offset()}")
for i in range(5):
order = {"order_id": i, "item": f"produk-{i}", "qty": i + 1}
producer.produce(
"orders",
key=str(i),
value=json.dumps(order),
callback=delivery_report,
)
producer.poll(0)
producer.flush()
Jalankan dengan python producer.py. Tiap pesan membawa key dan value — key dipakai Kafka untuk menentukan ke partition mana pesan mendarat, jadi pesan dengan key sama selalu jatuh ke partition yang sama. Callback delivery_report dipanggil setelah broker mengonfirmasi pesan tersimpan, sehingga kamu tahu offset persisnya, bukan cuma "kayaknya terkirim".
Menulis Consumer
Consumer membaca pesan dari topic. Buat consumer.py:
import json
from confluent_kafka import Consumer
consumer = Consumer({
"bootstrap.servers": "localhost:9092",
"group.id": "order-service",
"auto.offset.reset": "earliest",
})
consumer.subscribe(["orders"])
try:
while True:
msg = consumer.poll(1.0)
if msg is None:
continue
if msg.error():
print(f"Error: {msg.error()}")
continue
order = json.loads(msg.value())
print(f"Proses {order} dari partition {msg.partition()} offset {msg.offset()}")
except KeyboardInterrupt:
pass
finally:
consumer.close()
Perhatikan auto.offset.reset: earliest. Nilai ini menentukan dari mana consumer mulai membaca saat pertama kali join dan belum punya offset tersimpan — earliest berarti dari pesan paling awal, latest berarti hanya pesan baru. Jalankan sekali, kelima pesan yang tadi dikirim producer akan tercetak.
Consumer Group: Satu Pesan, Banyak Pembaca
Buka tiga terminal dan jalankan python consumer.py di ketiganya. Karena ketiganya memakai group.id yang sama (order-service), Kafka otomatis membagi-bagi partition di antara mereka. Kirim lagi lima pesan dari producer, lalu perhatikan: tidak ada satu pun pesan yang diproses dua kali. Itulah jaminan consumer group — tiap pesan hanya dikonsumsi satu anggota.
# terminal 1
python consumer.py
# terminal 2
python consumer.py
# terminal 3
python consumer.py
Kafka menjamin pemrosesan at-least-once, bukan exactly-once secara default. Kalau consumer mati tepat setelah membaca pesan tapi sebelum menyimpan offset, pesan itu bisa terproses dua kali saat consumer hidup lagi. Buat logika consumer yang idempoten — aman dijalankan ulang tanpa efek samping ganda.
Langkah Berikutnya: Jadikan Producer Sebuah API
Di dunia nyata, producer biasanya hidup di dalam aplikasi web — misalnya endpoint yang menerima pesanan lalu menulisnya ke Kafka. Kamu bisa membungkus kode producer tadi ke endpoint FastAPI, persis pola yang saya tunjukkan di Cara Membuat REST API dengan FastAPI. Coba dulu: jalankan tiga consumer, kirim sepuluh pesan, dan lihat bagaimana partisi membaginya ke tiap proses.