Dalam 15 menit ke depan, kita akan merakit sebuah sistem pemrosesan pesan asinkron berkecepatan tinggi menggunakan Apache Kafka dan producer/consumer berbasis Go dari nol sampai berjalan mulus. Bayangkan Anda sedang menghadapi lonjakan jutaan trafik di platform e-commerce saat flash sale; jika arsitektur backend Anda masih menggunakan HTTP synchronous standar atau antrean database biasa, dipastikan database akan down karena kehabisan koneksi. Di sinilah kekuatan Event-Driven Architecture (EDA) dengan Kafka menyelamatkan karir Anda, dan Go memberikan performa eksekusi tanpa block.
1. Menu Kita Hari Ini & Alat yang Perlu Disiapkan
Pernahkah Anda melihat sistem checkout yang sukses mencatat transaksi secara instan padahal email konfirmasi dan poin loyalty baru dikirim beberapa menit kemudian? Itu adalah keajaiban sistem asinkron. Hari ini kita tidak akan menghabiskan waktu berteori tentang definisi pesan atau broker, melainkan langsung membangun pipa penyalur data industri kelas berat di mesin lokal Anda.
Sebelum kita mulai meracik kodenya, pastikan beberapa bahan dapur utama sudah terpasang di terminal Anda:
- Go (Golang) versi 1.21 atau yang lebih baru (karena kita akan memanfaatkan manajemen concurrency bawaannya yang elegan).
- Docker & Docker Compose untuk menjalankan kluster Apache Kafka dan Zookeeper tanpa ribet.
- Kafka CLI tools atau extension VS Code jika ingin mengintip isi topik secara visual.
[!TIP] Pastikan port
9092untuk Kafka dan2181untuk Zookeeper tidak sedang digunakan oleh aplikasi lokal lain seperti database instans lama Anda.
Mari kita siapkan fondasi infrastruktur ini agar pipa data kita berdiri kokoh sebelum kita mulai menulis baris kode Go pertama.
2. Peta Jalan Singkat: Apa yang Sedang Kita Rakit
Untuk memahami bagaimana jutaan pesan bisa mengalir tanpa ada yang block, kita perlu melihat peta jalurnya secara utuh. Arsitektur kita hari ini dibagi menjadi tiga lapisan utama yang saling bahu-membahu:
- Producer (Go): Menerima payload request dari klien web, lalu secara asinkron mengirimkannya ke Kafka tanpa menunggu respons acknowledgment yang memblokir thread utama.
- Broker (Apache Kafka): Mengatur partisioning pesan berdasarkan kunci tertentu (misal:
user_id) agar urutan data tetap terjaga rapi di dalam partition, sekaligus mendistribusikannya ke Consumer Group. - Consumer (Go): Mengambil pesan secara paralel, melakukan proses bisnis berat, dan jika terjadi kegagalan sistemik, pesan tersebut akan dilempar secara otomatis ke Dead Letter Queue (DLQ) alih-alih membuat aplikasi crash berulang kali.
Dari peta jalan ini, mari kita langsung eksekusi langkah pertama dengan merakit file konfigurasi infrastruktur.
3. Langkah 1: Mempersiapkan Fondasi & Environment
Kita mulai dengan menyiapkan wadah penampung pesan kita. Buat sebuah direktori kerja baru di komputer Anda, lalu buat file bernama docker-compose.yml untuk menghidupkan Zookeeper dan Kafka.
version: '3.8'
services:
zookeeper:
image: confluentinc/cp-zookeeper:7.5.0
environment:
ZOOKEEPER_CLIENT_PORT: 2181
ZOOKEEPER_TICK_TIME: 2000
ports:
- "2181:2181"
kafka:
image: confluentinc/cp-kafka:7.5.0
depends_on:
- zookeeper
ports:
- "9092:9092"
environment:
KAFKA_BROKER_ID: 1
KAFKA_ZOOKEEPER_CONNECT: 'zookeeper:2181'
KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://localhost:9092
KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1
KAFKA_TRANSACTION_STATE_LOG_MIN_ISR: 1
KAFKA_TRANSACTION_STATE_LOG_REPLICATION_FACTOR: 1
Jalankan perintah docker-compose up -d di terminal Anda. Lihat responsnyaโjika kontainer menyala tanpa error, itu tanda bahwa fondasi broker pesan kita sudah siap menerima aliran data. Sekarang, saatnya kita masuk ke inti pemrograman Go untuk berinteraksi langsung dengan kluster Kafka ini.
4. Langkah 2: Meracik Konfigurasi Inti Tanpa Basa-basi
Inisialisasi modul Go baru dengan perintah go mod init kafka-event-driven. Kita akan menggunakan pustaka github.com/IBM/sarama (penerus pustaka Sarama dari Shopify) yang terkenal tangguh di lingkungan produksi berskala masif.
Jalankan go get github.com/IBM/sarama. Setelah itu, mari kita buat file producer.go untuk merancang asynchronous producer yang tidak akan membuat HTTP server Anda lag saat terjadi lonjakan trafik.
package main
import (
"encoding/json"
"log"
"os"
"os/signal"
"time"
"github.com/IBM/sarama"
)
type OrderEvent struct {
OrderID string `json:"order_id"`
UserID string `json:"user_id"`
Amount float64 `json:"amount"`
Timestamp time.Time `json:"timestamp"`
}
func main() {
config := sarama.NewConfig()
config.Producer.Return.Successes = true
config.Producer.RequiredAcks = sarama.WaitForLocal
config.Producer.Retry.Max = 5
producer, err := sarama.NewAsyncProducer([]string{"localhost:9092"}, config)
if err != nil {
log.Fatalf("Gagal membuat async producer: %v", err)
}
defer producer.Close()
// Menangani error dan success secara asynchronous agar goroutine utama tidak block
go func() {
for {
select {
case err := <-producer.Errors():
log.Printf("Gagal mengirim pesan: %v\n", err.Err)
case succ := <-producer.Successes():
log.Printf("Pesan sukses terkirim ke topik %s [partisi %d] offset %d\n", succ.Topic, succ.Partition, succ.Offset)
}
}
}()
signals := make(chan os.Signal, 1)
signal.Notify(signals, os.Interrupt)
// Simulasi pengiriman 5 juta pesan secara cepat tanpa block
for i := 1; i <= 5; i++ {
order := OrderEvent{
OrderID: "ORD-" + time.Now().Format("20060102150405") + "-" + string(rune(i)),
UserID: "USR-99",
Amount: 150.50,
Timestamp: time.Now(),
}
payload, _ := json.Marshal(order)
msg := &sarama.ProducerMessage{
Topic: "orders.created",
Key: sarama.StringEncoder(order.UserID),
Value: sarama.ByteEncoder(payload),
}
// Kirim ke kanal asinkron
producer.Input() <- msg
}
time.Sleep(2 * time.Second) // Menunggu goroutine callback menyelesaikan tugasnya
}
Perhatikan bagaimana kita memanfaatkan channel bawaan Go dan producer.Input() untuk mengirim data. Pemanggilan ini tidak menunggu jaringan merespons secara sinkron, sehingga latensi aplikasi web Anda tetap berada di level mikrodetik.
5. Langkah 3: Sentuhan Akhir Keamanan & Optimasi
Sebuah sistem streaming data tidak akan lengkap tanpa penanganan konsumen yang cerdas dan mekanisme pengamanan data gagal (Dead Letter Queue / DLQ). Buat file consumer.go untuk membaca pesan tersebut, memprosesnya melalui Consumer Group, dan mengalihkan pesan yang rusak ke DLQ.
package main
import (
"context"
"encoding/json"
"log"
"os"
"os/signal"
"sync"
"syscall"
"github.com/IBM/sarama"
)
type ConsumerGroupHandler struct{}
func (ConsumerGroupHandler) Setup(_ sarama.ConsumerGroupSession) error { return nil }
func (ConsumerGroupHandler) Cleanup(_ sarama.ConsumerGroupSession) error { return nil }
func (h ConsumerGroupHandler) ConsumeClaim(session sarama.ConsumerGroupSession, claim sarama.ConsumerGroupClaim) error {
for message := range claim.Messages() {
var data map[string]interface{}
if err := json.Unmarshal(message.Value, &data); err != nil {
log.Printf("[DLQ] Format pesan tidak valid, melempar ke Dead Letter Queue: %v", err)
// Di sini Anda bisa menambahkan logika meneruskan ke topik DLQ
session.MarkMessage(message, "")
continue
}
// Proses bisnis utama berhasil
log.Printf("Berhasil memproses Order ID: %v dari partisi %d", data["order_id"], message.Partition)
session.MarkMessage(message, "")
}
return nil
}
func main() {
config := sarama.NewConfig()
config.Consumer.Group.Rebalance.Strategy = sarama.NewBalanceStrategyRoundRobin()
config.Consumer.Offsets.Initial = sarama.OffsetOldest
client, err := sarama.NewConsumerGroup([]string{"localhost:9092"}, "order-processing-group", config)
if err != nil {
log.Fatalf("Gagal menginisialisasi consumer group: %v", err)
}
defer client.Close()
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
var wg sync.WaitGroup
ฤฑm, handler := client, ConsumerGroupHandler{}
wg.Add(1)
go func() {
defer wg.Done()
for {
if err := im.Consume(ctx, []string{"orders.created"}, handler); err != nil {
log.Printf("Error pada consumer: %v", err)
}
if ctx.Err() != nil {
return
}
}
}()
sigterm := make(chan os.Signal, 1)
signal.Notify(sigterm, syscall.SIGINT, syscall.SIGTERM)
<-sigterm
cancel()
wg.Wait()
}
Dengan membagi tugas melalui Consumer Group, Anda dapat melipatgandakan jumlah instans consumer Go di server berbeda tanpa khawatir pesan diproses dua kali.
6. Tes Rasa: Memastikan Semuanya Berjalan Sempurna
Saatnya menguji racikan kita di dapur lokal. Jalankan consumer terlebih dahulu pada terminal pertama:
go run consumer.go
Lalu, di terminal kedua, jalankan producer kita:
go run producer.go
Perhatikan layar consumer Anda. Pesan-pesan akan langsung terproses secara instan dengan distribusi partisi yang seimbang tanpa ada jeda atau blocking thread. Anda baru saja membangun jembatan data berkecepatan tinggi yang siap menampung jutaan transaksi harian.
7. Pertolongan Pertama: Jika Terjadi Kendala Kecil
[!WARNING] Jika Anda mendapati error
kafka: client has run out of available brokerssaat menjalankan program Go, itu biasanya karena konfigurasiKAFKA_ADVERTISED_LISTENERSdi Docker Compose masih menggunakanlocalhostatau belum dapat dijangkau oleh container lain. Pastikan host dan port sudah dipetakan dengan benar kelocalhost:9092.
Jika terjadi kendala rebalancing lag pada consumer group, pastikan Anda selalu memanggil session.MarkMessage(message, "") hanya setelah proses bisnis selesai dengan sukses. Selamat mencoba, dan nikmati performa arsitektur event-driven tanpa hambatan!