# Membangun Event-Driven Architecture Tangguh dengan RabbitMQ & Go

> Diterbitkan oleh Literally Web ID pada 21 Oct 2026
> Sumber: https://literally.web.id/articles/membangun-event-driven-architecture-tangguh-dengan-rabbitmq-go

---

Dalam 15 menit ke depan, kita akan merakit sebuah sistem pesan asinkron yang benar-benar tangguh di atas meja kerja kita, menggabungkan ketangguhan RabbitMQ dengan kecepatan Go. Bayangkan Anda sedang membangun backend e-commerce yang harus memproses ribuan transaksi per detik tanpa membuat database utama Anda megap-megap karena lonjakan trafik. Kita tidak akan berbicara teori abstrak hari ini; kita langsung terjun ke dapur produksi untuk meracik arsitektur event-driven yang kebal badai trafik.

## 1. Menu Kita Hari Ini & Alat yang Perlu Disiapkan

Sebelum kita menyalakan terminal, mari pastikan bahan-bahan di dapur kita lengkap. Kita butuh Go versi 1.21 atau yang lebih baru, Docker untuk menjalankan instance RabbitMQ secara instan tanpa mengotori sistem operasi lokal, dan sedikit kesabaran arsitektural. Di akhir sesi ini, kita bukan cuma punya script yang jalan, tapi sebuah pola pikir bagaimana mendesain pipeline pesan yang tahan terhadap kegagalan jaringan, lonjakan beban, hingga error tak terduga di sisi consumer.

> [!TIP]
> Pastikan port 5672 (AMQP) dan 15672 (Management UI) di mesin lokal Anda tidak sedang digunakan oleh aplikasi lain sebelum kita menarik container Docker.

Menyiapkan fondasi dengan benar adalah kunci agar proses peracikan selanjutnya berjalan mulus tanpa gangguan teknis yang tidak perlu, mari kita mulai dengan menyiapkan environment-nya.

## 2. Peta Jalan Singkat: Apa yang Sedang Kita Rakit

Perjalanan kita terbagi menjadi tiga fase utama yang saling mengunci. Pertama, kita akan mengatur *Exchange and Binding Topologies* di RabbitMQ agar pesan mengalir ke rute yang tepat menggunakan Direct dan Topic exchange. Kedua, kita merancang jaring pengaman berupa *Dead-Letter Queue Pattern* untuk menangkap pesan yang gagal diproses agar tidak hilang begitu saja. Ketiga, kita menulis kode consumer di Go yang menerapkan *Idempotent Consumer Implementation* guna memastikan tidak ada duplikasi data meskipun pesan dikirim ulang berkali-kali.

> [!IMPORTANT]
> Arsitektur event-driven yang baik bukanlah yang tidak pernah gagal, melainkan yang tahu persis apa yang harus dilakukan ketika kegagalan itu terjadi.

Dengan peta jalan yang jelas ini, mari kita langsung eksekusi langkah pertama di terminal kesayangan kita.

## 3. Langkah 1: Mempersiapkan Fondasi & Environment

Langkah pertama, mari kita hidupkan server RabbitMQ kita menggunakan Docker Compose agar reproduksibel di mana saja, baik di laptop Anda maupun di server staging. Buat sebuah file bernama `docker-compose.yml` di direktori kerja Anda:

```yaml
version: '3.8'

services:
  rabbitmq:
    image: rabbitmq:3.12-management
    container_name: rabbitmq_event_driven
    ports:
      - "5672:5672"
      - "15672:15672"
    environment:
      RABBITMQ_DEFAULT_USER: "architect"
      RABBITMQ_DEFAULT_PASS: "supersecret"
    healthcheck:
      test: ["CMD", "rabbitmq-diagnostics", "ping"]
      interval: 10s
      timeout: 5s
      retries: 3
```

Jalankan perintah `docker compose up -d` di terminal Anda. Lihat responsnya: container langsung berputar dan panel management bisa diakses melalui `http://localhost:15672` dengan kredensial `architect` / `supersecret`. Fondasi sudah berdiri kokoh, saatnya kita meracik konfigurasi intinya.

## 4. Langkah 2: Meracik Konfigurasi Inti Tanpa Basa-basi

Sekarang kita masuk ke inti arsitektur: mendefinisikan topologi exchange, queue utama, dan yang paling penting, *Dead-Letter Queue (DLQ)*. Di dunia nyata, pesan bisa gagal diproses karena database down atau payload korup. Kita tidak boleh membiarkan pesan ini masuk ke infinite loop.

Mari buat direktori project Go baru, inisialisasi modul (`go mod init event-demo`), dan instal driver resmi `streadway/amqp` atau `amqp091-go`. Berikut adalah kode untuk inisialisasi topologi kita di Go (`main.go`):

```go
package main

import (
	"log"
	"github.com/streadway/amqp"
)

func main() {
	conn, err := amqp.Dial("amqp://architect:supersecret@localhost:5672/")
	if err != nil {
		log.Fatalf("Gagal terhubung ke RabbitMQ: %v", err)
	}
	defer conn.Close()

	ch, err := conn.Channel()
	if err != nil {
		log.Fatalf("Gagal membuka channel: %v", err)
	}
	defer ch.Close()

	// 1. Deklarasi Dead-Letter Exchange & Queue
	err = ch.ExchangeDeclare("dlx.events", "direct", true, false, false, false, nil)
		checkErr(err, "Gagal deklarasi DLX")

	_, err = ch.QueueDeclare("dlq.orders", true, false, false, false, nil)
		checkErr(err, "Gagal deklarasi DLQ")

	err = ch.QueueBind("dlq.orders", "order.failed", "dlx.events", false, nil)
		checkErr(err, "Gagal binding DLQ")

	// 2. Deklarasi Main Exchange & Queue dengan argumen DLX
	err = ch.ExchangeDeclare("app.events", "topic", true, false, false, false, nil)
		checkErr(err, "Gagal deklarasi Main Exchange")

	args := amqp.Table{
		"x-dead-letter-exchange":    "dlx.events",
		"x-dead-letter-routing-key": "order.failed",
	}

	_, err = ch.QueueDeclare("queue.orders", true, false, false, false, args)
		checkErr(err, "Gagal deklarasi Main Queue")

	err = ch.QueueBind("queue.orders", "order.created", "app.events", false, nil)
		checkErr(err, "Gagal binding Main Queue")

	log.Println("Topologi RabbitMQ berhasil diracik dan disinkronkan!")
}

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

Jalankan `go run main.go`. Perhatikan bagaimana script ini langsung mendeklarasikan infrastruktur perpesanan secara deklaratif. Jika terjadi kesalahan pada payload di `queue.orders`, RabbitMQ secara otomatis akan melemparkannya ke `dlq.orders` berkat argumen `x-dead-letter-exchange` yang kita pasang. Konfigurasi ini membawa kita langsung ke lapisan keamanan dan optimasi berikutnya.

## 5. Langkah 3: Sentuhan Akhir Keamanan & Optimasi

Sebuah sistem pesan tidak akan tangguh tanpa penanganan *Idempotent Consumer*. Di jaringan yang tidak stabil, pesan yang sama bisa terkirim dua kali. Jika consumer kita tidak idempoten, saldo pengguna bisa terpotong dua kali atau pesanan tercatat ganda.

Mari kita lengkapi consumer Go kita dengan pengecekan idempotensi menggunakan Redis atau pengecekan unik database, serta mekanisme *manual acknowledgement (ack/nack)*:

```go
// Cuplikan logika consumer idempoten
msgs, err := ch.Consume(
	"queue.orders", // queue
	"order-worker-1", // consumer tag
	false, // auto-ack dimatikan! Kita pakai manual ack
	false, // exclusive
	false, // no-local
	false, // no-wait
	nil,
)

go func() {
	for d := range msgs {
		// Simulasi Cek Idempotensi (misal: cek UUID pesan di Redis)
		isProcessed := checkIdempotencyKey(d.CorrelationId)
		if isProcessed {
			// Pesan sudah pernah diproses, langsung Ack agar hilang dari antrean
			d.Ack(false)
			continue
		}

		err := processOrder(d.Body)
		if err != nil {
			// Jika gagal permanen, reject tanpa requeue agar masuk ke DLQ
			d.Nack(false, false)
			continue
		}

		// Berhasil
		saveIdempotencyKey(d.CorrelationId)
		d.Ack(false)
	}
}()
```

Dengan mematikan `auto-ack` dan menggunakan `d.Nack(false, false)`, kita menyerahkan pesan bermasalah langsung ke Dead-Letter Queue tanpa memblokir antrean utama. Sistem tetap berjalan mulus meskipun ada satu dua pesan yang korup.

## 6. Tes Rasa: Memastikan Semuanya Berjalan Sempurna

Saatnya melakukan uji coba lapangan. Kirimkan pesan uji coba menggunakan script publisher Go atau langsung melalui RabbitMQ Management UI dengan routing key `order.created`. Buka tab Queues di `http://localhost:15672`, amati bagaimana pesan masuk ke `queue.orders`, ditarik oleh consumer Go Anda, dan diproses dengan bersih.

> [!TIP]
> Gunakan fitur `Publish Message` di Management UI untuk mengirim payload JSON kosong atau korup, lalu saksikan bagaimana pesan tersebut secara otomatis dilarikan ke `dlq.orders` tanpa membuat aplikasi Go Anda *panic* atau *crash*.

Jika metrik menunjukkan pesan berpindah sesuai jalur topologi yang kita buat, racikan arsitektur Anda sudah siap dibawa ke lingkungan production.

## 7. Pertolongan Pertama: Jika Terjadi Kendala Kecil

Di lapangan, kadang kita menemui kendala sepele seperti error `ACCESS_REFUSED` saat koneksi. Ini biasanya karena kesalahan kredensial atau vhost default. Cek kembali string koneksi Anda. Jika consumer mendadak berhenti merespons, periksa apakah ada *unacked messages* menumpuk akibat lupa memanggil `d.Ack()`. Selalu pantau channel menggunakan `rabbitmqctl list_channels` di dalam container untuk memastikan tidak ada *channel leak* yang menggerus memori server Anda. Selamat merakit infrastruktur tangguh!