M
Mr Sugiarto
Developer
17 Jun 2026 7 min read

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.created baru dikirim setelah payment.completed — bukan saat checkout
  • product.price_updated dikonsumsi 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

  1. KrakenD adalah satu-satunya pintu masuk — service tidak ada yang expose port ke luar kecuali lewat gateway
  2. Webhook Midtrans tidak lewat KrakenD — langsung ke Payment Service (akan dibahas di Chapter 08)
  3. Setiap service memiliki database sendiri — tidak ada foreign key antar database
  4. order.created baru dikirim setelah payment.completed — ini keputusan desain yang penting
  5. gRPC untuk sync (butuh jawaban langsung), RabbitMQ untuk async (tidak butuh jawaban langsung)
M
Mr Sugiarto

Developer

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
Newsletter

Dapatkan artikel terbaru langsung di email Anda