Golang Event-Driven Microservice Part 1 - Overview...
Overview
Dalam tutorial ini kita akan membangun sebuah sistem ecommerce menggunakan Event-Driven Microservice Architecture dengan bahasa pemrograman Go (Golang) dan RabbitMQ sebagai message broker. Sistem dipecah menjadi 5 service independen yang berkomunikasi lewat event asinkron menggunakan RabbitMQ, serta komunikasi sinkron menggunakan gRPC.
Sebelum mulai menulis kode program, kita harus tahu terlebih dahulu apa yang akan dibuat dan kenapa didesain seperti itu. Pahami chapter ini dengan baik — chapter-chapter berikutnya akan lebih mudah dipahami jika gambaran besarnya sudah jelas.
5 Service yang Kita Bangun
Sistem ecommerce ini dipecah menjadi 5 service kecil yang masing-masing memiliki tanggung jawab sendiri:
| Service | Tugasnya |
|---|---|
| Catalog Service | Menyimpan informasi produk, menampilkan daftar & detail produk |
| Order Service | Mengurus keranjang belanja dan proses checkout |
| Payment Service | Integrasi Midtrans, terima webhook pembayaran |
| Inventory Service | Mengurus stok, reservasi, dan audit pergerakan barang |
| Notification Service | Kirim notifikasi ke user setiap ada kejadian penting |
Kenapa dipecah? Karena jika satu sistem besar (monolith), ketika stok service membutuhkan update, seluruh sistem harus di-deploy ulang. Dengan microservice, setiap bagian bisa berkembang dan di-deploy sendiri-sendiri.
Bagaimana Mereka Berkomunikasi?
Ada tiga cara komunikasi di sistem ini, dan masing-masing memiliki alasan tersendiri:
1. REST — Untuk Client ke Server
Semua request dari browser/mobile masuk lewat KrakenD dulu (API Gateway), baru diteruskan ke service yang tepat.
Browser → KrakenD :8080 → Catalog Service :8081
2. gRPC — Untuk Komunikasi Internal yang Butuh Jawaban Langsung
Ketika Order Service butuh tahu stok tersedia atau tidak, tidak dapat menunggu lama, sehingga menggunakan gRPC yang lebih cepat dari REST.
Order Service → gRPC → Inventory Service (CheckStock)
Order Service → gRPC → Payment Service (GenerateSnapToken)
3. RabbitMQ — Untuk Kejadian yang Tidak Butuh Jawaban Langsung
Setelah pembayaran berhasil, Order Service tidak perlu menunggu Inventory selesai update stok. Cukup publish event ke RabbitMQ, lalu Inventory dan Notification service akan membacanya sendiri kapanpun mereka siap.
Payment Service → publish "payment.completed" → RabbitMQ
↓
Order Service consume
Inventory Service consume
Notification Service consume
Kapan Pakai REST, gRPC, atau Event?
| Kondisi | Pilihan | Contoh |
|---|---|---|
| Request dari client (browser/mobile) | REST | GET /products, POST /checkout |
| Butuh jawaban langsung, komunikasi internal | gRPC | CheckStock, GenerateSnapToken |
| Tidak butuh jawaban langsung, bisa async | Event (RabbitMQ) | payment.completed, order.created |
| Data bisa stale sebentar, volume tinggi | Event | stock.reserved, notifikasi |
Aturan sederhananya: jika harus menunggu jawaban sebelum lanjut → gRPC. Jika bisa lanjut tanpa menunggu → Event.
Database & Storage per Service
Setiap service memiliki database sendiri — tidak ada yang boleh akses database service lain secara langsung.
| Service | Database | Tabel Utama |
|---|---|---|
| Catalog | PostgreSQL + Redis | products · Redis key: catalog:product:{id} TTL 1 jam, catalog:products:list:{hash} TTL 5 menit |
| Order | PostgreSQL | orders, order_items, events_outbox |
| Payment | PostgreSQL | payments, payments_outbox |
| Inventory | PostgreSQL | products_stock, stock_reservations, stock_movements |
| Notification | PostgreSQL | notifications |
Kenapa tidak ada foreign key lintas database?
Karena foreign key hanya bisa bekerja dalam satu database. Jika Order Service menyimpan FK ke tabel products milik Catalog Service, keduanya harus berada di database yang sama — dan itu melanggar prinsip database per service. Referensi antar service dilakukan lewat product_id (UUID) yang di-snapshot saat checkout.
Alur Checkout dari Awal sampai Akhir
Ini bagian yang paling penting dipahami. Catat baik-baik.
1. User klik "Checkout"
→ POST /orders/cart/checkout (lewat KrakenD)
→ Order Service validasi keranjang, snapshot harga produk saat ini
2. Order Service cek stok ke Inventory via gRPC
→ Jika stok kurang: langsung balik 422, proses berhenti
3. Order Service minta token Midtrans ke Payment Service via gRPC
→ Payment Service call Midtrans Snap API
→ Midtrans memberikan snap_token
→ snap_token dikembalikan ke user
4. User buka popup Midtrans di browser → bayar
5. Midtrans kirim webhook ke Payment Service
→ Payment Service verifikasi tanda tangan (SHA512)
→ Update status pembayaran jadi COMPLETED
→ Publish event "payment.completed" ke RabbitMQ
6. Order Service membaca event payment.completed
→ Update status order: PENDING_PAYMENT → CONFIRMED
→ Publish event "order.created"
7. Inventory Service membaca event payment.completed
→ Kurangi stok (reservasi)
→ Publish event "stock.reserved"
8. Notification Service baca semua event
→ Simpan notifikasi ke database
→ User bisa lihat notifikasi di app
Kenapa order.created baru dikirim SETELAH payment.completed? Jika order.created dikirim duluan, Inventory akan reserve stok padahal user belum tentu bayar. Ini bisa membuat stok "hilang" sementara untuk order yang akhirnya dibatalkan.
Event Catalog
Berikut daftar lengkap semua event yang beredar di sistem. Ini adalah referensi utama yang akan terus dipakai sepanjang tutorial ini.
| Event | Publisher | Consumer | Trigger |
|---|---|---|---|
payment.completed |
Payment Service | Order, Inventory, Notification | Midtrans webhook settlement |
payment.failed |
Payment Service | Order, Notification | Midtrans webhook deny/cancel/expire |
order.created |
Order Service | Inventory, Notification | Setelah payment.completed diterima |
order.cancelled |
Order Service | Inventory, Notification | Setelah payment.failed atau stock.insufficient |
stock.reserved |
Inventory Service | Notification | Stok berhasil direservasi |
stock.insufficient |
Inventory Service | Order, Notification | Stok tidak cukup saat reservasi |
product.price_updated |
Catalog Service | Catalog Service (self) | Admin update harga produk |
Pola penamaan queue: {consumer}.{event}.queue
Contoh: order.payment-completed.queue, inventory.payment-completed.queue
Tiga hal yang perlu dicatat:
order.createdbaru dikirim setelahpayment.completed— bukan saat checkoutproduct.price_updateddikonsumsi oleh service yang sama (Catalog) untuk invalidate Redis cache- Notification Service berlangganan hampir semua event — dia yang paling banyak tahu kejadian di sistem
Status Order
Order memiliki alur status yang sederhana tetapi penting:
DRAFT → PENDING_PAYMENT → CONFIRMED
↘ CANCELLED
- DRAFT: Pengguna sedang menambahkan item ke keranjang
- PENDING_PAYMENT: Checkout sudah dilakukan, menunggu user bayar di Midtrans
- CONFIRMED: Pembayaran berhasil dikonfirmasi
- CANCELLED: Pembayaran gagal/expire, atau stok tidak cukup setelah dikonfirmasi
Kenapa Pakai Redis di Catalog?
Setiap kali user buka halaman produk, sistem harus query ke database. Jika ada 1000 user buka halaman yang sama dalam satu menit, database menerima 1000 query untuk data yang sama.
Redis dipakai sebagai cache. Data produk disimpan sementara di Redis, jadi query cukup sekali ke database, sisanya ambil dari Redis yang jauh lebih cepat.
GET /products/{id}
→ Cek Redis dulu
→ Jika ada (HIT): langsung balik ke user, response header X-Cache: HIT
→ Jika tidak ada (MISS): query PostgreSQL → simpan ke Redis → balik ke user
Jika admin update harga produk, cache di Redis harus dihapus agar user tidak dapat data lama. Ini dilakukan lewat event product.price_updated.
Struktur Monorepo
Semua service ada dalam satu repository:
ecommerce/
├── services/
│ ├── catalog-service/
│ ├── order-service/
│ ├── payment-service/
│ ├── inventory-service/
│ └── notification-service/
├── gateway/ → konfigurasi KrakenD
├── proto/ → definisi gRPC (Protobuf)
├── contracts/ → OpenAPI spec & event schema
├── infra/ → docker-compose, Grafana, Loki
└── .github/workflows → CI/CD pipeline
Setiap service dibuat dari boilerplate gohexaclean yang sudah menggunakan pola Hexagonal + Clean Architecture. Jadi struktur folder setiap service konsisten dan kita bisa fokus ke logika bisnis.
Tech Stack Sekilas
| Komponen | Teknologi | Kenapa |
|---|---|---|
| Bahasa | Go 1.24 | Performa tinggi, cocok untuk microservice |
| HTTP Framework | GoFiber | Cepat, syntax familiar |
| Database | PostgreSQL 16 | Satu DB per service, tidak ada FK lintas service |
| Cache | Redis 7 | Cache produk di Catalog Service |
| Message Broker | RabbitMQ 3.13 | Komunikasi async antar service |
| Internal RPC | gRPC + Protobuf | Komunikasi sync internal yang cepat |
| API Gateway | KrakenD CE | Satu pintu masuk, JWT, rate limit |
| Payment | Midtrans Snap | Payment gateway lokal yang populer |
| Monitoring | Grafana + Loki | Log terpusat dari semua service |
| CI/CD | GitHub Actions | Otomatis test & deploy per service |
Yang Perlu Diingat dari Chapter Ini
- KrakenD adalah satu-satunya pintu masuk — service tidak ada yang expose port ke luar kecuali lewat gateway
- Webhook Midtrans tidak lewat KrakenD — langsung ke Payment Service (akan dibahas di Chapter 08)
- Setiap service memiliki database sendiri — tidak ada foreign key antar database
- order.created baru dikirim setelah payment.completed — ini keputusan desain yang penting
- gRPC untuk sync (butuh jawaban langsung), RabbitMQ untuk async (tidak butuh jawaban langsung)
Bagian dari Series: Belajar Golang Event-Driven Microservice: Studi Kasus Ecommerce
Panduan lengkap membangun sistem toko online nyata dengan Go — mulai dari keranjang belanja, pembayaran Midtrans, komunikasi antar service, hingga mon...
Lihat Series Lengkap