Ahmad Habibi
Sistem notifikasi cukup penting untuk menginformasikan berbagai kegiatan atau pengumuman kepada user, seperti rilis produk baru, pesan dari seseorang, dan lain - lain.
Namun, dalam pembuatan nya terkadang terasa menyulitkan bagi developer. Karena harus memikirkan bagaimana user menerima notifikasi secara real-time dan sinkron.
Tapi, kali ini kita akan membuatnya dengan cukup mudah dengan menggunakan fitur Pub/Sub dari Redis.
Mari kita mulai!
Step 1 - Persiapan dan Setup Project
Mari kita siapkan dari 0, project-nya gak bakal banyak dependency tentunya.
Membuat project baru
Pertama, buat folder bernama pubsub-go atau bebas terserah andaBuka Terminal atau CMD, lalu ketik perintah ini:
mkdir pubsub-go
Setelah itu, masuk ke folder tersebut dengan cd dan lakukan inisialisasi project Golang.Ketik perintah ini:
go mod init pub-sub-go
Perintah ini akan membuat file baru dengan nama go.mod
dan go.sum
yang merupakan file untuk menyimpan data project dan dependency yang dibutuhkan.
Install dependency yang dibutuhkan
go get -u github.com/gofiber/fiber/v2go get -u github.com/gofiber/template/html/v2go get -u github.com/redis/go-redis/v9go get -u github.com/valyala/fasthttp
Membuat folder dan file
Buat folder dan file seperti berikut:
Step 2 - Setup Redis
Kali ini kita kan menggunakan Docker sebagai tempat untuk menginstall Redis, tapi jika tidak mau menggunakan Docker anda bisa menginstall langsung ke Sistem Operasi anda.
Oke, kita lanjut dengan membuat file baru di root direktori dengan nama docker-compose.yml
dan isi dengan kode berikut:
version: "3.9"services: redis: image: redis container_name: pubsubgo-redis ports: - 6379:6379 volumes: - ./_docker-data/redis:/data
Ini adalah konfigurasi untuk menjalankan service Redis dengan Docker compose. Volume nya sengaja di taruh pada direktori _docker-data
(terdapat underscore pada awal penamaan) untuk menghindari direktori nya terdeteksi sebagai bagian dari project Golang.
Setelah membuat docker-compose.yml , berikutnya jalankan service Docker dengan perintah
docker-compose up -d
Sekarang kita bisa mengakses Redis pada port 6379
Step 3 - Menyambungkan aplikasi dengan Redis
Buka file configs/redis.go
, kemudian isi dengan kode berikut:
package configsimport ( "context" "github.com/redis/go-redis/v9")const ( REDIS_CHANNEL_NOTIFICATION = `notification`)var RDS *redis.Clientvar RDS_CTX context.Contextfunc InitRedisClient() { RDS_CTX = context.Background() RDS = redis.NewClient(&redis.Options{ Addr: `localhost:6379`, Password: ``, DB: 0, })}
Kode di atas berfungsi untuk mengkoneksikan Redis pada host/port localhost:6379
dan pada database nomor 0
, kemudian koneksi tersebut disimpan pada variabel var RDS *redis.Client
.
Kalau sudah, lanjut kita tes koneksinya dengan memanggil fungsi tersebut di main.go
.
package mainimport ( "fmt" "pub-sub-go/configs")func main() { configs.InitRedisClient() err := configs.RDS.Ping(configs.RDS_CTX).Err() if err != nil { fmt.Println(`failed to connect redis`) panic(err) } else { fmt.Println(`connect to redis successfully !`) }}
Kemudian jalankan dengan perintah berikut:
go run main.go
Jika muncul pesan connect to redis successfully !, maka aplikasi berhasil terhubung ke Redis.
Step 4 - Membuat controller
Kita akan membuat 2 route/endpoint yang mana 1 route untuk menampilkan halaman web, dan 1 nya lagi untuk menerima notifikasi secara real-time dengan metode Server-Sent Event.
Kedua endpoint yang kita buat akan mengimplementasikan fitur Pub/Sub pada Redis.
Tulis kode berikut pada file main.go
package mainimport ( "log" "pub-sub-go/configs" "pub-sub-go/controller" "github.com/gofiber/fiber/v2" "github.com/gofiber/template/html/v2")func main() { configs.InitRedisClient() err := configs.RDS.Ping(configs.RDS_CTX).Err() if err != nil { log.Println(`failed to connect redis`) panic(err) } engine := html.New(`./views`, `.html`) app := fiber.New(fiber.Config{ Views: engine, }) app.Listen(`:8080`)}
Pada variable engine := html.New(`./views`, `.html`)
berguna untuk mengkonfigurasi direktori dan ekstensi file untuk me-render halaman web. Pada kasus ini kita gunakan direktori ./views
dan ekstensi .html.
Lalu, apa itu Pub/Sub ?
Pub/Sub, singkatan dari “Publish/Subscribe”, merupakan pola dalam pemrograman yang memungkinkan pengiriman pesan dari satu komponen aplikasi ke satu atau lebih komponen lain tanpa keduanya terhubung langsung atau memiliki ketergantungan satu sama lain.
Pub/Sub biasanya sering digunakan pada notifikasi, aplikasi chat, dan lain sebagainya.
Pub/Sub di Redis
Redis mengimplementasikan pola Pub/Sub melalui sistem pesan yang sederhana dan efisien antar client. Di Redis, client - client dapat “publish” pesan ke saluran/channel yang bernama, sem*ntara client lain dapat “subscribe” channel tersebut untuk menerima pesan.
Ketika sebuah pesan di publish ke channel, Redis akan mengirimkan pesan tersebut ke semua client yang “subscribe” ke channel tersebut. [^1]
Jadi, sudah paham kan apa itu Pub/Sub.
Sekarang mari kita lanjutkan, tulis kode berikut pada file controller/home.go
package controllerimport ( "fmt" "log" "pub-sub-go/configs" "github.com/gofiber/fiber/v2")func HomePage(c *fiber.Ctx) error { message := fmt.Sprintf("User dengan IP %v mengakses website", c.IP()) err := configs.RDS.Publish( configs.RDS_CTX, configs.REDIS_CHANNEL_NOTIFICATION, message, ).Err() if err != nil { log.Println(`Error:`, err) } return c.Render(`index`, fiber.Map{})}
Kode di atas akan mem-publish sebuah pesan ke channel bernama notifikasi
kemudian me-render halaman web pada file index.html
. Bila diperhatikan, cukup menulis nama file nya saja (index
) tanpa menulis ekstensi-nya karena sudah kita konfigurasi pada file main.go
.
Balik ke file main.go, Panggil fungsi di atas untuk dijadikan handler/controller pada endpoint /
:
app := fiber.New(fiber.Config{ Views: engine,})app.Get(`/`, controller.HomePage)
Lanjut ke file controller/notification.go
, tulis kode berikut:
package controllerimport ( "bufio" "fmt" "log" "pub-sub-go/configs" "github.com/gofiber/fiber/v2" "github.com/valyala/fasthttp")func Notification(c *fiber.Ctx) error { c.Set("Content-Type", "text/event-stream") c.Set("Cache-Control", "no-cache") c.Set("Connection", "keep-alive") c.Set("Transfer-Encoding", "chunked") c.Context().SetBodyStreamWriter(fasthttp.StreamWriter(func(w *bufio.Writer) { pubSub := configs.RDS.Subscribe(configs.RDS_CTX, configs.REDIS_CHANNEL_NOTIFICATION) defer pubSub.Close() for { msg, err := pubSub.ReceiveMessage(configs.RDS_CTX) if err != nil { log.Println(`pubSub.ReceiveMessage(CTX)`, err) } else { log.Println(`message:`, msg.Payload) } fmt.Fprintf(w, "data: %s\n\n", msg.Payload) err = w.Flush() if err != nil { fmt.Printf("Error while flushing: %v. Closing http connection.\n", err) break } } })) return nil}
Kode di atas berfungsi untuk mengirimkan pesan yang didapat dari men-subscribe ke channel bernama notifikasi
secara terus menerus atau streaming, dengan metode Server-Sent Event.
Jadi, apa itu Server-Sent Event?
Server-Sent event (SSE) adalah sebuah teknologi web yang memungkinkan server untuk mengirimkan data secara asynchronous kepada client melalui protokol HTTP. SSE dirancang khusus untuk memfasilitasi komunikasi satu arah dari server ke client, yang bukan merupakan permintaan cepat dari klien.
Pada controller di atas, SSE ditandai dengan pengaturan header dengan memanggil fungsi c.Set("Content-Type", "text/event-stream")
, dan c.Context().SetBodyStreamWriter(...)
.
Lanjut ke file main.go
, kita panggil controller di atas untuk digunakan pada endpoint /notifikasi
.
app := fiber.New(fiber.Config{ Views: engine,})app.Get(`/`, controller.HomePage)app.Get(`/notification`, controller.Notification)
Sip, controller untuk “publish” dan “subscribe” sudah jadi.
Step 5 - Membuat tampilan halaman web
Kita akan membuat tampilan yang sederhana saja, yang penting bisa menampilkan notifikasi.
Tulis kode berikut pada file views/index.html
:
<!DOCTYPE html><html lang="id"><head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>{{ .title }}</title></head><body> <h1>Notifikasi</h1> <div id="result"> </div> <script> if (typeof(EventSource) !== "undefined") { const source = new EventSource("/notification"); source.onmessage = function(event) { document.getElementById("result").innerHTML += `<p style="margin: 0">` + event.data + `</p>`; }; } else { document.getElementById("result").innerHTML = "Maaf, browser anda tidak mendukung SSE."; } </script></body></html>
Syntax {{ .title }}
akan me-render nilai dari key title dari object yg kita buat pada controller dari file controller/home.go
.
Kemudian di bagian script nya kita menginisialisasikan source dari Event (SSE) yang merujuk ke endpoint /notification
.
Bisa kita lihat pada baris kode ini:
const source = new EventSource("/notification");
Kemudian kita bisa buat fungsi untuk handle event onmessage
pada object source
.
source.onmessage = function(event){ ... }
Jadi fungsi hendler event onmessage
ini akan dieksekusi setiap kali data diterima. Lalu akan menambah elemen baru ke dalam elemen dengan id=”result”
, yang berisi data dari event tersebut.
Step 6 - Testing aplikasi
Jika sudah, selanjutnya mari kita jalankan aplikasinya dengan perintah
go run main.go
Kemudian buka 2 browser ke alamat localhost:8080
.
Saya mencoba membuka browser yang sebelah kiri dahulu kemudian yang kanan. Jadinya yang kiri saja yang menerima notifikasi karena sistem “publish” nya terjadi saat si user mengakses halaman utama web tersebut.
Apa Selanjutnya?
Sejauh ini kita sudah berhasil membuat sistem notifikasi dengan fitur Pub/Sub di Redis dengan bahasa pemrograman Golang.
Kita sudah memahami:
- Apa itu Server-Sent Event
- Apa itu Pub/Sub
Sekarang anda bisa bebas mengkreasikan pembuatan notifikasi sesuka anda.Untuk melihat source code-nya, bisa anda buka link berikut: