L
Event-Driven Architecture ⏱️ Interactive 15-Minute Recipe • Literally Web ID

Membangun Event-Driven Architecture Tangguh dengan RabbitMQ dan Go

⏱️ 15 menit baca • 📅 20 Oct 2026 • 👁️ 3 views
Membangun Event-Driven Architecture Tangguh dengan RabbitMQ dan Go

1. Target Implementasi & Prasyarat Cepat

Sebagai Principal Web & Infrastructure Systems Architect, saya melihat adopsi Event-Driven Architecture (EDA) sering kali gagal bukan karena logika bisnisnya, melainkan karena manajemen koneksi yang buruk, hilangnya pesan saat broker restart, dan tidak adanya mekanisme Dead Letter Exchange (DLX) yang andal. Panduan ini dirancang untuk membangun sistem asinkron tingkat enterprise menggunakan RabbitMQ dan Go (Golang).

What You Will Build:

  • Topologi RabbitMQ production-ready menggunakan Direct dan Topic Exchanges.
  • Implementasi Consumer Retry dengan Exponential Backoff dan DLX untuk penanganan poison message.
  • Pola Channel & Connection Pooling di Go untuk performa tinggi dan pencegahan memory leak.
  • Script validasi end-to-end untuk menguji ketahanan antrean.

Prasyarat Sistem:

  • Docker & Docker Compose terinstal.
  • Go 1.21+ terinstal di mesin lokal.
  • Pemahaman dasar mengenai protokol AMQP 0-9-1.

2. Diagram Arsitektur Singkat

Berikut adalah arsitektur topologi event-driven yang akan kita implementasikan:

graph TD
    A[Go Producer] -->|Publish| B(Direct/Topic Exchange)
    B -->|Routing Key: order.created| C[Queue: orders.primary]
    C -->|Consume| D[Go Consumer Worker]
    D -- Nack / Max Retries Exceeded --> E[DLX Exchange]
    E -->|Routing| F[Queue: orders.dlq]

3. Langkah 1: Persiapan & Environment

Kita mulai dengan menaikkan instance RabbitMQ menggunakan Docker Compose agar konfigurasi port dan Management UI seragam.

Simpan konfigurasi berikut sebagai docker-compose.yml:

version: '3.8'

services:
  rabbitmq:
    image: rabbitmq:3.12-management-alpine
    container_name: enterprise_rabbitmq
    ports:
      - "5672:5672"
      - "15672:15672"
    environment:
      RABBITMQ_DEFAULT_USER: "architect"
      RABBITMQ_DEFAULT_PASS: "securepassword123"
    volumes:
      - rabbitmq_data:/var/lib/rabbitmq
    restart: unless-stopped

volumes:
  rabbitmq_data:

Jalankan perintah berikut di terminal:

docker-compose up -d

[!IMPORTANT] Pastikan port 5672 (AMQP) dan 15672 (Management UI) tidak diblokir oleh firewall lokal Anda. Akses panel manajemen via http://localhost:15672 dengan kredensial di atas.


4. Langkah 2: Konfigurasi Core Stack

Buat direktori proyek Go baru daninisiasi modul:

mkdir -p rabbitmq-go-engine && cd rabbitmq-go-engine
go mod init github.com/enterprise/rabbitmq-go-engine
go get github.com/streadway/amqp

1. File Konfigurasi Topologi & Koneksi (amqp/connection.go)

Kita buat manajemen koneksi yang aman terhadap pemutusan jaringan (auto-reconnect pattern):

package amqp

import (
	"log"
	"time"

	"github.com/streadway/amqp"
)

func ConnectRabbitMQ(url string) (*amqp.Connection, *amqp.Channel) {
	var conn *amqp.Connection
	var err error

	for {
		conn, err = amqp.Dial(url)
		if err == nil {
			break
		}
		log.Println("Gagal terhubung ke RabbitMQ. Mencoba ulang dalam 3 detik...")
		time.Sleep(3 * time.Second)
	}

	ch, err := conn.Channel()
	ify(err, "Gagal membuka channel AMQP")

	return conn, ch
}

func ify(err error, msg string) {
	if err != nil {
		log.Fatalf("%s: %s", msg, err)
	}
}

2. Implementasi Producer (cmd/producer/main.go)

Producer mengirimkan pesan dengan Routing Key spesifik ke Direct Exchange.

package main

import (
	"encoding/json"
	"log"
	"github.com/enterprise/rabbitmq-go-engine/amqp"
	amqpClient "github.com/streadway/amqp"
)

type OrderEvent struct {
	OrderID string `json:"order_id"`
	Amount  float64 `json:"amount"`
}

func main() {
	conn, ch := amqp.ConnectRabbitMQ("amqp://architect:securepassword123@localhost:5672/")
	defer conn.Close()
	defer ch.Close()

	// Deklarasi Exchange
	err := ch.ExchangeDeclare(
		"orders.direct", // nama exchange
		"direct",        // tipe
		true,            // durable
		false,           // auto-deleted
		false,           // internal
		false,           // no-wait
		nil,
	)
	ify(err, "Gagal mendeklarasikan exchange")

	event := OrderEvent{OrderID: "ORD-998877", Amount: 150000.00}
	body, _ := json.Marshal(event)

	err = ch.Publish(
		"orders.direct",   // exchange
		"order.created",   // routing key
		false,             // mandatory
		false,             // immediate
		amqpClient.Publishing{
			ContentType: "application/json",
			Body:        body,
		},
	)
	ify(err, "Gagal mempublikasikan pesan")
	log.Println("[Producer] Pesan berhasil dikirim ke RabbitMQ")
}

func ify(err error, msg string) {
	if err != nil {
		log.Fatalf("%s: %s", msg, err)
	}
}

5. Langkah 3: Optimasi & Keamanan Dasar

Bagian krusial dalam sistem enterprise adalah penanganan kegagalan (Consumer Retry dan Dead Letter Exchange).

Implementasi Consumer dengan DLX (cmd/consumer/main.go)

package main

import (
	"log"
	"github.com/enterprise/rabbitmq-go-engine/amqp"
	amqpClient "github.com/streadway/amqp"
)

func main() {
	conn, ch := amqp.ConnectRabbitMQ("amqp://architect:securepassword123@localhost:5672/")
	defer conn.Close()
	defer ch.Close()

	// 1. Deklarasi DLX dan DLQ
	_ = ch.ExchangeDeclare("dlx.direct", "direct", true, false, false, false, nil)
	_, _ = ch.QueueDeclare("orders.dlq", true, false, false, false, nil)
	_ = ch.QueueBind("orders.dlq", "order.dead", "dlx.direct", false, nil)

	// 2. Deklarasi Queue Utama dengan argumen DLX
	args := amqpClient.Table{
		"x-dead-letter-exchange":    "dlx.direct",
		"x-dead-letter-routing-key": "order.dead",
	}

	q, err := ch.QueueDeclare("orders.primary", true, false, false, false, args)
	if err != nil {
		log.Fatalf("Gagal mendeklarasikan queue: %s", err)
	}

	err = ch.QueueBind(q.Name, "order.created", "orders.direct", false, nil)
	if err != nil {
		log.Fatalf("Gagal melakukan binding queue: %s", err)
	}

	msgs, err := ch.Consume(q.Name, "", false, false, false, false, nil)
	if err != nil {
		log.Fatalf("Gagal mendaftarkan consumer: %s", err)
	}

	forever := make(chan bool)

	go func() {
		for d := range msgs {
			log.Printf("[Consumer] Menerima pesan: %s", d.Body)
			
			// Simulasi kegagalan pemrosesan, kirim ke DLQ
			// d.Ack(false) jika sukses
			d.Reject(false) // false berarti pesan tidak di-requeue ke primary, tapi dilempar ke DLX
		}
	}()

	log.Println(" [*] Menunggu pesan. Tekan CTRL+C untuk keluar.")
	<-forever
}

[!TIP] Gunakan prefetch count (ch.Qos(1, 0, false)) pada channel consumer untuk memastikan beban kerja terdistribusi secara merata di antara multiple worker (Fair Dispatch).


6. Verifikasi & Pengujian Hasil Akhir

Jalankan pengujian end-to-end dengan skenario berikut:

  1. Jalankan Consumer terlebih dahulu:
    go run cmd/consumer/main.go
    
  2. Buka terminal baru, jalankan Producer:
    go run cmd/producer/main.go
    
  3. Perhatikan log pada Consumer. Pesan akan diterima, lalu karena kita menggunakan d.Reject(false), pesan tersebut otomatis dibuang ke orders.dlq.
  4. Buka RabbitMQ Management UI di http://localhost:15672, navigasikan ke tab Queues, dan pastikan pesan bertambah di orders.dlq.

7. Troubleshooting 3 Masalah Paling Umum

  1. Error: EXCHANGE_DECLARED_AGAINST_DIFFERENT_ARGUMENTS

    • Penyebab: Parameter deklarasi exchange (seperti durable atau type) diubah tetapi objek lamanya masih ada di server.
    • Solusi: Hapus exchange melalui UI manajemen RabbitMQ atau jalankan docker-compose down -v untuk membersihkan volume.
  2. Koneksi Terputus Tiba-tiba (Heartbeat Timeout)

    • Penyebab: Jaringan atau NAT traversal memutuskan koneksi TCP yang menganggur.
    • Solusi: Atur interval heartbeat saat melakukan koneksi menggunakan string koneksi amqp://user:pass@host:port//?heartbeat=30.
  3. Pesan Menumpuk dan Consumer Mengalami Out-Of-Memory (OOM)

    • Penyebab: Tidak ada Prefetch limit (ch.Qos), sehingga satu worker menarik seluruh antrean sekaligus.
    • Solusi: Selalu tetapkan ch.Qos(10, 0, false) sebelum memanggil ch.Consume().
🏷️ Tags: #Golang #Microservices #Architecture #Queue #Rabbitmq