Materi ini membahas pola pemrosesan request secara asynchronous di level arsitektur aplikasi/API — bukan soal event loop/async programming di level bahasa pemrograman (topik itu ada di catatan 03_Asynchronous_Programming). Fokusnya adalah keputusan desain: kapan sebuah service sebaiknya merespons synchronous (client menunggu hasil akhir) vs asynchronous (client hanya diberi tahu request diterima, hasil sebenarnya menyusul), dan pola-pola apa saja yang dipakai untuk mengetahui hasil eksekusi ketika prosesnya tidak selesai seketika.

Cara Menghubungkan Services: Kenapa Response Diperlukan

Sebuah response dari server ke client diperlukan untuk tiga alasan:

  • Menginformasikan request sudah diterima.
  • Berisi data yang diperlukan client.
  • Berisi informasi kegagalan yang terjadi.

Synchronous API dilakukan dengan cara client menunggu server selesai memproses — disebut juga blocking request. Ini adalah pola standar yang dipakai HTTP & REST: client mengirim request, server menghitung (calculate the response), baru mengirim response kembali; sepanjang waktu itu client diam menunggu.

Masalah: Bagaimana Jika Eksekusi Request Memerlukan Waktu Lama

Slide memberi contoh pengiriman email: request untuk “deliver an email” ternyata butuh melewati banyak service lain secara berurutan — sender’s mail server → mail relay → receiver’s mail server — sebelum akhirnya ada konfirmasi “email received” yang mengalir balik ke client. Jika pola synchronous dipertahankan di sini, client harus menunggu seluruh rangkaian proses tersebut selesai sebelum mendapat response, padahal client mungkin tidak butuh tahu hasilnya secara langsung/real-time — cukup tahu bahwa permintaannya sudah “dipegang” oleh sistem.

Alternatif Asynchronous

Sebagai gantinya, server bisa langsung merespons begitu request diterima (bukan setelah selesai dieksekusi), lalu proses sebenarnya (mengirim email lewat mail relay, dst.) berjalan di belakang layar.

  • Pro: client dapat langsung melanjutkan hal lain tanpa harus menunggu proses panjang selesai.
  • Cons: client tidak mengetahui apakah request tersebut nantinya benar-benar berhasil dieksekusi atau tidak — response “OK, I’ll try!” hanya berarti request sudah diterima, bukan jaminan sukses.

Perbedaan Makna Response: Synchronous vs Asynchronous

Poin pentingnya: pada kedua kasus, response tetap dikirimkan ke client, dan keduanya bisa sama-sama diimplementasikan dengan HTTP/REST. Perbedaannya bukan pada mekanisme transport, melainkan pada makna semantik dari response itu sendiri:

AspekSynchronousAsynchronous
RequestDeliver an emailDeliver an email
Makna responseDelivery acknowledge — konfirmasi bahwa email benar-benar sudah terkirimRequest acknowledge — konfirmasi bahwa request sudah diterima, belum tentu sudah dieksekusi
Client menunggu hinggaProses selesai sepenuhnya (blocking)Request diterima saja (tidak blocking terhadap hasil akhir)
Kepastian hasil bagi clientLangsung pasti (sukses/gagal diketahui saat itu juga)Tidak pasti — perlu mekanisme tambahan untuk tahu hasil akhirnya
Kompleksitas desainLebih sederhana, mengikuti pola HTTP/REST standarLebih kompleks — perlu desain tambahan (status tracking, callback, atau side channel)
Kecepatan respons ke clientLambat jika proses backend lamaCepat, karena client tidak menunggu proses backend selesai
Contoh use caseQuery data, validasi login, operasi cepat lainnyaTransfer file besar, pembuatan VM, checkout, booking tiket

Bagaimana Jika Client Perlu Mengetahui Hasilnya

Model asynchronous yang dijelaskan sebelumnya (kirim request, dapat ACK penerimaan, lalu tidak ada follow-up lagi) disebut pola fire and forget. Namun pada banyak kasus nyata, client tetap perlu tahu hasil akhir dari request yang dikirimkan — apakah sukses, gagal, atau errornya apa. Untuk itu ada tiga opsi/variasi pola yang bisa dipakai di atas dasar fire-and-forget.

Opsi 1: Request Record

Mekanismenya:

  • Server menyimpan sebuah request record pada database ketika request pertama kali diterima, lalu mengembalikan sebuah unique request ID ke client.
  • Ketika eksekusi request akhirnya selesai (baik sukses maupun gagal), server mengupdate request record tersebut di database dengan status terbaru.
  • Client kemudian dapat mengecek hasil kapan saja dengan cara polling menggunakan request ID yang sudah didapat sebelumnya.

Contoh alur konkret dari slide:

POST /messageAttempt        → {"email_id": 4390293}
GET /message/status/4390293 → {"status": "pending"}
GET /message/status/4390293 → {"status": "failed", "error": "invalid address"}

Client mengirim request pembuatan pesan, mendapat ID 4390293, lalu berulang kali melakukan GET ke endpoint status menggunakan ID tersebut sampai statusnya berubah dari pending menjadi hasil akhir (failed/success). Pola ini cocok ketika client tidak selalu online mendengarkan, tapi bersedia melakukan polling berkala.

Opsi 2: Callback ke Client (Webhook)

Mekanismenya:

  • Client menyediakan fungsi callback (dikenal sebagai webhook) — yaitu sebuah URL/endpoint milik client sendiri — yang disertakan dalam request awal.
  • Server, setelah selesai memproses request (berapa pun lama waktunya), secara aktif memanggil balik (POST) ke URL callback tersebut untuk mengirimkan hasil akhirnya.
  • Syarat penting: pola ini hanya bisa dilakukan jika client dapat listen response — artinya client harus berupa proses/service yang terus berjalan (long-running) dan dapat diakses dari jaringan (tidak terhalang NAT/firewall di sisi client), karena server-lah yang perlu menginisiasi koneksi baru ke client.

Contoh alur dari slide:

Client sends: POST /messageAttempt
              {"callback": "http://3.3.3.3:80/messageComplete"}
... beberapa waktu kemudian ...
Backend sends: POST /messageComplete

Client mengirim request berikut alamat callback-nya, mendapat ACK (“OK, I’ll try!”) segera, lalu di kemudian hari — setelah proses pengiriman email benar-benar selesai — server (backend) balik memanggil endpoint /messageComplete milik client untuk memberi tahu hasilnya.

Opsi 3: Side Channel

Mekanismenya:

  • Kita tetap memakai model fire and forget murni sebagai jalur utama — asalkan tingkat kegagalan (failure rate) jarang terjadi.
  • Ketika kegagalan memang terjadi, informasi mengenai kegagalan tersebut dikirimkan lewat channel lain di luar jalur request-response utama, bukan lewat status tracking maupun callback.

Contoh konkret dari slide:

  • Menggunakan email untuk mengirimkan notifikasi jika terjadi kegagalan pengiriman order (misalnya kegagalan charge kartu kredit pada proses “purchase book”).
  • Menyimpan log error untuk keperluan monitoring oleh tim operasional, bukan langsung dikonsumsi oleh client.

Perhatikan bahwa pada diagram di atas, alur normalnya tetap sama seperti fire-and-forget biasa (Purchase book → OK, I'll try!), tapi ada jalur tambahan Email: charge failed yang hanya muncul kondisional saat terjadi kegagalan — inilah “side channel”-nya.

Ringkasan Perbandingan Tiga Pola

PolaMekanisme intiSiapa yang inisiasi cek statusSyarat/prasyaratCocok untuk
Request RecordSimpan status di DB + request ID, client pollingClient (aktif mengecek)Client tahu request ID, mau pollingClient tidak selalu online, hasil tidak butuh notifikasi instan
Callback (Webhook)Server memanggil balik URL callback milik clientServer (aktif memberi tahu)Client harus long-running & reachable dari jaringan (tidak di belakang NAT/firewall)Integrasi antar service/backend, client berupa server lain
Side ChannelFire-and-forget, notifikasi kegagalan lewat channel lain (email, log)Server, tapi hanya saat gagalFailure rate harus jarangOperasi yang umumnya reliable, kegagalan adalah kasus minoritas

Contoh Request yang Cocok Menggunakan Asynchronous Processing

Beberapa jenis request yang secara alami cocok diproses secara asynchronous karena butuh waktu lama, melibatkan banyak resource, atau hasilnya tidak perlu diketahui client secara instan:

  • Transfer file (terutama file besar).
  • Mengambil file dari tape storage (media penyimpanan lambat).
  • Pembuatan virtual machine (misalnya pada cloud provider).
  • Membeli barang pada shopping cart (checkout).
  • Book tiket pesawat, bioskop, atau konser.
  • Request connection pada social media (permintaan pertemanan/follow).
  • Pengiriman mobile push notification ke user lain.
  • Pengiriman text/SMS.
  • Copy sebuah tweet ke ribuan/jutaan followers.

Message Queue: Menyediakan Asynchronicity & Decoupling

Jika client benar-benar tidak perlu status response (fire and forget murni), request cukup disimpan pada sebuah queue untuk diproses nanti oleh service lain, alih-alih diproses langsung saat itu juga.

Keuntungan menambahkan pesan ke queue:

  • Cepat, karena operasinya hanya berupa kopi data ke antrian — tanpa parsing atau eksekusi business logic sama sekali di titik ini.
  • Menghindari perlambatan pada upstream service akibat kepadatan di downstream — artinya service yang menerima request awal (upstream) tidak ikut melambat meskipun service yang benar-benar memproses (downstream) sedang sibuk/tertunda. Queue berperan sebagai buffer sehingga sistem dapat menangani burst pendek (lonjakan request sesaat) yang sebenarnya melebihi kapasitas pemrosesan normal sistem.

Message Queue Menyimpan Request yang Siap Diproses

Konsep pentingnya: menyimpan request pada queue itu esensinya sama seperti membuat sebuah request API biasa.

  • Isi dari pesan (message) itu sendiri yang mendefinisikan request-nya — bisa dianggap message format == API.
  • Artinya, aturan/skema format pesan yang disepakati berfungsi sama seperti kontrak API — producer dan consumer harus sepakat soal struktur data di dalam pesan, persis seperti API contract antara client dan server.

Queue Terminology

  • Producer: pihak yang push/publish/produce message ke queue.
  • Consumer: pihak yang pull/pop/consume/subscribe message dari queue.
  • Sebuah message queue dapat dipartisi menjadi beberapa virtual queue dengan cara mendefinisikan topic pada setiap pesan — sehingga consumer dapat subscribe hanya pada topic tertentu saja, tidak harus menerima semua pesan yang masuk ke queue.

Contoh Kasus: Alur Publish Tweet dengan Message Queue

Slide memberi contoh end-to-end yang menunjukkan bagaimana beberapa queue bisa dirangkai bertahap dalam satu arsitektur:

  1. Client posts request — mobile app mengirim POST /tweet ke Twitter API. API service (a) mengirim request ke auth service untuk validasi token (auth service mengecek ke Account DB), (b) menyimpan job post tweet ke work queue untuk diproses belakangan, lalu (c) langsung mengirim response sukses 200 OK ke user — semua ini terjadi sebelum tweet benar-benar dipublikasikan ke followers.
  2. Publication service memproses request — service ini mengambil job dari work queue, mengambil daftar followers dari Followers DB, menambahkan tweet baru ke semua follower’s feed (disimpan di Feed DB), lalu mengantrikan task baru ke notification queue untuk memberi notifikasi ke setiap @mention dan followers yang mengaktifkan alert.
  3. Notification service memberikan alert ke user — mengambil task dari notification queue satu per satu (bisa 0 hingga jutaan item tergantung jumlah followers/mentions), lalu mengirimkan notifikasi lewat email/mobile device (data diambil dari email & mobile dev. DB). Jika terjadi kegagalan pengiriman notifikasi, task diulang beberapa kali, dan jika tetap gagal, bisa diabaikan — konsisten dengan sifat notifikasi yang best-effort, bukan critical.

Pola ini menunjukkan bagaimana satu request awal (POST /tweet) di-decouple menjadi rangkaian tahapan independen yang masing-masing punya queue-nya sendiri (work queue → notification queue), sehingga setiap tahap bisa diproses dan di-scale terpisah tanpa membuat client menunggu keseluruhan rangkaian selesai.

Tradeoff: Tightly Coupled vs Loosely Coupled

  • Tightly coupled (synchronous) service lebih mudah dirancang dan dibangun — alurnya linear dan mudah dipahami: request masuk, diproses, response keluar, selesai.
  • Loosely coupled (asynchronous) service dapat lebih cepat (dari sudut pandang client yang tidak perlu menunggu proses penuh), namun perlu strategi eksplisit untuk menangani kegagalan, karena kegagalan tidak lagi otomatis terlihat oleh client saat itu juga. Tiga opsi penanganan kegagalan pada model asynchronous:
    • Kegagalan dapat ditolerir/diabaikan — dianggap acceptable loss (relevan untuk kasus seperti notifikasi non-kritis).
    • Error disimpan pada DB untuk dicek kemudian — namun ini berarti lebih sulit melakukan penanganan error secara tepat waktu (delayed error handling).
    • Error dapat menimbulkan semacam alert ke user — mirip pola side channel yang sudah dibahas sebelumnya.
AspekTightly Coupled (Synchronous)Loosely Coupled (Asynchronous)
Kemudahan desain & implementasiLebih mudah — alur linear, mengikuti pola HTTP/REST standarLebih kompleks — perlu desain tambahan (queue, status tracking, callback)
Kecepatan respons ke clientTerbatas oleh waktu proses backend (client menunggu penuh)Cepat — client hanya menunggu ACK penerimaan
Visibilitas kegagalanLangsung terlihat client saat itu juga (dalam response)Tidak langsung; butuh mekanisme tambahan (request record/callback/side channel) atau ditoleransi
Ketergantungan antar serviceErat (tightly coupled) — kegagalan downstream langsung memengaruhi callerLonggar (loosely coupled) — service dapat berjalan/gagal independen berkat buffer queue
SkalabilitasSulit menyerap burst request karena tiap request menahan resource sampai selesaiLebih mudah menyerap burst — queue meredam lonjakan (smoothing)
Contoh penggunaanREST API standar, validasi login, query dataTransfer file besar, pembuatan VM, checkout, booking tiket, publish tweet ke jutaan follower

Decoupling Mendukung Scalability

Message queue pada dasarnya adalah sejenis database sederhana yang tugas utamanya menyimpan request/job. Karakteristik penting yang mendukung skalabilitas:

  • Many producers dan many consumers dapat tersambung ke satu queue yang sama secara bersamaan.
  • Basic queue berjalan pada satu mesin, sedangkan distributed queue berjalan pada cluster (banyak mesin).
  • Queue dapat berperan sebagai semacam load balancer — mendistribusikan pekerjaan ke banyak consumer.
  • Producer dan consumer dapat di-scale secara independen — jumlah producer dan jumlah consumer tidak harus sama atau berubah bersamaan; masing-masing bisa ditambah/dikurangi sesuai bebannya sendiri.
  • Queue smooths/melandaikan peak permintaan dengan cara menunda pemrosesan — request yang masuk saat beban tinggi tetap ditampung di queue, tidak langsung ditolak atau membuat sistem downstream kewalahan.

Active vs Passive Queue

AspekActive QueuePassive Queue
Arah pengiriman pesanQueue mengetahui kemana harus mengirim pesan dan secara aktif melakukan push ke subscriberQueue hanya menerima dan menyimpan pesan hingga diminta — tidak tahu/tidak peduli siapa yang akan mengambilnya
Peran consumer/subscriberHarus listen message yang dikirim (event-driven)Harus secara periodik melakukan request/poll pesan sendiri
Pola push/pullProducer pushes, dan queue pushes ke consumerProducer pushes, tapi consumer pulls
Analogi implementasiSistem notifikasi/event busSpecialized DB — bisa diimplementasikan sebagai DB table biasa

Queue pada Berbagai Level Arsitektur

Ada tiga level di mana konsep queue dapat diimplementasikan, dari yang paling sederhana hingga paling kompleks:

  • In-app queue — sebuah aplikasi membuat queue internal untuk menyimpan job yang akan dikerjakan belakangan, mungkin dieksekusi pada thread yang berbeda dalam proses yang sama. Contoh: ExecutorService pada Java yang di dalamnya berisi work queue.
  • Separate queueing app — sebuah proses terpisah yang berjalan dan siap melakukan push/fetch pesan melalui koneksi jaringan. Seringkali proses ini dijalankan pada node yang sama dengan aplikasi utama, sehingga tetap bisa memakai komunikasi lokal (lebih cepat, tapi masih proses yang berbeda).
  • Distributed message queue — sebuah cluster yang mengimplementasikan queue yang scalable dan robust. Contoh: ActiveMQ, Kafka, RabbitMQ, AWS SQS.

Pros & Cons Tiap Level

LevelProsCons
In-app queueSederhana; tidak memerlukan deployment aplikasi lainUmumnya tidak persisten — saat aplikasi crash, message yang ada di queue dapat hilang
Separate queueing appDapat dijalankan pada VM yang sama dengan aplikasi utama; dapat menyimpan message ke file (lebih persisten dari in-app)Scalability terbatas satu mesin; kegagalan machine/disk dapat membuat message hilang
Distributed message queueMassively scalable; messages direplikasi pada banyak node; menyediakan satu titik koordinasi yang sama untuk semua producer & consumerComplexity tinggi; ada consistency side effect — misalnya pada RabbitMQ, kita harus memilih delivery guarantee “at least once” atau “at most once”, namun tidak bisa “exactly once”

Back Pressure

Pertanyaan kuncinya: apa yang terjadi jika queue penuh?

  • Queue mungkin memberikan pesan error saat producer mencoba menambahkan entri baru ke queue yang sudah penuh.
  • Hal ini dapat mengakibatkan layanan terhenti atau error di sisi producer/upstream.
  • Devops/operations harus memonitor ukuran queue secara berkelanjutan untuk mengantisipasi masalah ini sebelum terjadi.
  • Pada autoscaling system, jumlah worker/backend (consumer) dapat di-scale otomatis berdasarkan panjang queue — semakin panjang antrian, semakin banyak consumer yang dijalankan untuk mengejar backlog.

Singkatnya, back pressure adalah mekanisme “sinyal balik” ketika kapasitas queue terlampaui: pilihannya adalah scaling (tambah consumer/kapasitas) atau reject dengan error (menolak request baru sampai ada ruang).

Catatan Penting: Queue Bukan Pengganti Frontend

Message Queue memiliki perilaku seperti layanan backend lain (misalnya database) — tidak dirancang untuk menerima koneksi langsung dari ribuan client sekaligus. Karena itu, arsitektur yang benar tetap harus punya frontend/API service yang menerima request dari client (biasanya lewat HTTP/REST), lalu service itulah yang menyimpan request tersebut ke queue — bukan client yang langsung berkomunikasi dengan message queue.

Flashcard

flashcards Apa perbedaan makna response antara synchronous dan asynchronous processing, meskipun keduanya sama-sama bisa dikirim lewat HTTP/REST? :: Pada synchronous, response berarti “delivery acknowledge” (proses benar-benar sudah selesai/berhasil); pada asynchronous, response hanya berarti “request acknowledge” (request sudah diterima, belum tentu sudah dieksekusi/berhasil). Apa itu pola “fire and forget”, dan apa masalah utamanya? :: Pola di mana client hanya mengirim request dan mendapat ACK penerimaan tanpa follow-up lebih lanjut. Masalah utamanya: client tidak pernah tahu apakah request tersebut akhirnya benar-benar berhasil dieksekusi atau gagal. Jelaskan mekanisme pola Request Record untuk mengetahui hasil asynchronous request. :: Server menyimpan request record di database dan mengembalikan unique request ID ke client saat request diterima; setelah eksekusi selesai, server mengupdate record tersebut; client kemudian melakukan polling (GET) menggunakan request ID untuk mengecek status terkini (pending/success/failed). Apa syarat utama agar pola Callback (webhook) bisa dipakai, dan bagaimana mekanismenya? :: Client harus menyediakan URL callback dan harus bisa listen response — artinya client harus long-running dan reachable dari jaringan (tidak terhalang NAT/firewall). Server, setelah selesai memproses, secara aktif memanggil balik (POST) ke URL callback tersebut untuk mengirim hasil. Kapan pola Side Channel cocok dipakai, dan berikan contohnya. :: Cocok ketika failure rate jarang terjadi, sehingga model fire-and-forget tetap dipertahankan sebagai jalur utama, dan informasi kegagalan (jika terjadi) dikirim lewat channel lain di luar jalur utama — misalnya notifikasi email saat charge kartu gagal, atau pencatatan log error untuk monitoring. Mengapa menambahkan pesan ke message queue itu cepat, dan apa manfaatnya bagi upstream service? :: Karena menambahkan pesan ke queue hanya berupa kopi data tanpa parsing atau eksekusi business logic. Manfaatnya, upstream service tidak ikut melambat akibat kepadatan di downstream, sehingga sistem bisa menyerap burst request pendek yang melebihi kapasitas normal. Apa perbedaan active queue dan passive queue? :: Active queue mengetahui tujuan pengiriman dan secara aktif push pesan ke subscriber (subscriber harus listen); passive queue hanya menerima dan menyimpan pesan hingga diminta, sehingga consumer harus secara periodik melakukan poll/pull sendiri (queue berperan seperti specialized DB/DB table). Sebutkan tiga level arsitektur penggunaan queue beserta satu contohnya masing-masing. :: In-app queue (contoh: ExecutorService pada Java), separate queueing app (proses terpisah yang push/fetch lewat koneksi jaringan, sering di node yang sama), dan distributed message queue (cluster scalable, contoh: ActiveMQ, Kafka, RabbitMQ, AWS SQS). Apa itu back pressure pada message queue, dan apa dua opsi penanganannya? :: Back pressure adalah kondisi ketika queue penuh sehingga producer bisa menerima error saat menambah entri baru. Dua opsi penanganannya: melakukan scaling (menambah consumer/kapasitas berdasarkan panjang queue, misalnya lewat autoscaling) atau menolak (reject) request baru dengan error. Mengapa message queue tidak bisa langsung menggantikan API service sebagai penerima request dari client? :: Karena message queue berperilaku seperti layanan backend lain (mis. database) yang tidak dirancang menerima koneksi langsung dari ribuan client sekaligus, sehingga tetap dibutuhkan frontend/API service yang menerima request client lalu menyimpannya ke queue.