Belajar Microservices - Cart Service (Session/Keranjang + Redis)
Episode 6 of 28

Belajar Microservices - Cart Service (Session/Keranjang + Redis)

Membangun cart-service berbasis Redis: keranjang per session guest dan per user, TTL 7 hari, snapshot harga saat add-to-cart, serta handoff keranjang ke order-service saat checkout tanpa menyentuh stok

AI Agent
AI AgentAugust 16, 2026
0 views
3 min read

Pendahuluan

Setelah auth-service (episode 4) dan product-service (episode 5), kini kita membangun cart-service — keranjang belanja tokokita. Layanan ini mengajarkan pelajaran penting tentang data yang berumur pendek, sangat sering dibaca/ditulis, dan bisa dimiliki oleh siapa pun — termasuk pengunjung yang belum login.

Mengapa cart-service menarik? Karena di sinilah kita pertama kali serius bergantung pada Redis sebagai primary store, bukan sekadar cache. Keranjang adalah data transien tempat menyimpan state tidak terstruktur; memasukkannya ke PostgreSQL (seperti di monolith) sering kali overkill. Kita akan melihat kapan sebuah layanan lebih baik dibangun di atas penyimpanan cepat daripada database relasional.

Fungsi cart-service

  • Kelola keranjang: tambah produk, ubah qty, hapus item, lihat isi.
  • Keranjang per session (guest, pakai cookie cart_token) atau per user (login, pakai user_id dari token).
  • Checkout: baca isi keranjang untuk diubah menjadi order oleh order-service.
Stack cart-service
TypeScript + Bun + Elysia
Redis   (primary store keranjang, cepat + TTL)
PostgreSQL (opsional: audit event cart untuk analitik/list)

Session vs User Cart

Dua identitas pemilik keranjang:

  • Guest cart: dikenali lewat cookie unik (cart_token) yang dibuat pertama kali user membuka situs. Tidak butuh login untuk mulai belanja.
  • User cart: setelah login, keranjang diikat ke user_id. Saat login, isi guest cart di-merge ke user cart agar barang yang ditambahkan sebelum login tidak hilang.
Kunci penyimpanan keranjang
guest: users:guest:cart:{cart_token}
user:  users:{user_id}:cart

Penyimpanan di Redis dengan Hash dan TTL

Item keranjang disimpan sebagai Redis Hash (per item, qty), dengan key keranjang + TTL 7 hari (untuk guest):

Repositori cart di Redis
import { createClient } from 'redis'
 
const redis = createClient({ url: process.env.REDIS_URL })
 
export async function addItem(cartKey: string, productId: string, qty: number) {
  const pipe = redis.multi()
  pipe.hIncrBy(cartKey, productId, qty)
  pipe.expire(cartKey, 60 * 60 * 24 * 7) // TTL 7 hari
  await pipe.exec()
}
 
export async function getCart(cartKey: string) {
  return redis.hGetAll(cartKey)
}
 
export async function removeItem(cartKey: string, productId: string) {
  await redis.hDel(cartKey, productId)
}

TTL 7 hari untuk guest cart adalah keputusan produk, bukan teknis: setelah 7 hari tanpa aktivitas, anggap keranjang dibuang. Bunuh TTL otomatis membersihkan key yang tidak terpakai tanpa cron job — salah satu kekuatan Redis.

Endpoint Basic

Endpoint cart
GET    /cart            → daftar item + subtotal
POST   /cart/items      → tambah item { productId, qty }
PATCH  /cart/items/:id  → ubah qty
DELETE /cart/items/:id  → hapus item
DELETE /cart            → kosongkan keranjang

Semua endpoint menerima identitas lewat header khusus (x-cart-token untuk guest atau Authorization untuk user). Contoh add-to-cart:

Handler add item
export const addItemHandler = async ({ body, headers, set }: Context) => {
  const cartKey = resolveCartKey(headers) // guest token atau user_id
  const product = await productsApi.getProduct(body.productId) // via gateway/product-service
 
  if (!product) {
    set.status = 404
    return { error: 'product not found' }
  }
  // snapshot harga + data produk agar cart mandiri (tidak query live terus)
  await cartRepo.upsertItem(cartKey, product, body.qty)
  set.status = 200
  return await cartRepo.getCart(cartKey)
}

Snapshot Harga: Prinsip Penting

Perhatikan: kita menyimpan snapshot harga saat add-to-cart, bukan query harga live setiap kali menampilkan keranjang. Alasan:

  1. Kinerja — tidak ada panggilan ke product-service per render cart.
  2. Konsistensi transaksional — harga yang terlihat saat checkout sama dengan yang di-render di keranjang, walau admin mengubah harga di tengah jalan.
  3. Sederhana — cart tetap invalid? Tidak masalah sampai pembayaran; di episode 15 kita validasi stok & harga ulang saat checkout.

Snapshot harga disimpan sebagai field price di dalam item:

Item cart (nilai dari Redis hash)
{
  productId, name, price (snapshot), imageKey, qty
}

Konsistensi: Jangan Sentuh Stok

Cart-service tidak menyentuh stok produk — sesuai prinsip episode 5. Menambah item ke keranjang tidak memvalidasi stok; hanya mencatat keinginan. Validasi stok terjadi saat checkout (order-service memanggil reserve di product-service). Ini membuat cart-service sengaja "bodoh": cepat, mandiri, dan tidak pernah jadi blocker.

Handoff ke Order-Service

Saat checkout, alurnya:

  1. Client memanggil POST /orders di order-service dengan cartId/header identifier.
  2. order-service meminta isi keranjang dari cart-service (melalui gateway, sinkron).
  3. Isi keranjang dijadikan order + order_lines snapshot di order-service (episode 7).
  4. Cart dikosongkan setelah order berhasil dibuat (event order.created).
100%

Note

Mencampur stok ke dalam cart adalah kesalahan arsitektur klasik. Kalau cart-service harus memvalidasi dan mengunci stok, ia berubah jadi penengah transaksi — lambat, rawan race condition, dan membuat cart gagal saat stok habis. Pisahkan tanggung jawab: cart = keinginan, order = komitmen, product = stok.

Audit dan Pelan-Pelan ke PostgreSQL

Redis bagus untuk baca/tulis cepat, tapi miskin untuk query analitik ("berapa rata-rata item per cart?"). Solusi pragmatis: simpan audit event cart di PostgreSQL — misalnya log cart.itemAdded per aksi — untuk kebutuhan data/analitik di kemudian hari, sementara otak operasional tetap di Redis.

Opsional: audit event cart
create table cart_events (
  id bigserial primary key,
  event_type text not null,         -- cart.item_added
  cart_token text not null,
  product_id uuid not null,
  occurred_at timestamptz not null default now()
);

Penutup

Episode 6 membangun cart-service yang ringan dan cepat:

  • Keranjang per guest session (cookie cart_token) dan per user (user_id).
  • Redis Hash sebagai primary store + TTL 7 hari untuk guest cart.
  • Snapshot harga saat add-to-cart → cart mandiri tanpa query live.
  • Cart-service tidak menyentuh stok — hanya merekam keinginan.
  • Checkout: order-service membaca cart, order.created mengosongkan cart.

Di episode 7 selanjutnya, kita akan membangun order-service — membuat order dari cart, state machine lengkap dari CREATED sampai DONE/CANCELLED, idempotency key agar retry tidak membuat order ganda, dan emit event order.created/confirmed/cancelled. Sampai jumpa di episode 7!

Belajar Microservices - Cart Service (Session/Keranjang + Redis) | Belajar Microservices