Skip to main content
System design langkah demi langkah · Pelajaran 6 dari 7 · 13 menit

Microservice untuk Pemula: Batas Service, Sync vs Async, Queue

Belajar microservice dari dasar: kapan modular monolith cukup, cara menarik batas service, dan kapan panggilan sebaiknya sync, async, atau lewat queue.

Oleh Matthews WongDitinjau pada

Poin utama

Microservice adalah service yang di-deploy terpisah, masing-masing memegang satu kemampuan bisnis beserta datanya; garis di sekeliling tiap service disebut batas service. Tarik batas itu lebih dulu di dalam modular monolith, pecah hanya jika ada kendala nyata, lalu pilih per panggilan: rantai sinkron mengalikan availability, sedangkan 202 Accepted dengan queue membiarkan pekerjaan lambat menunggu dan diulang.

Setelah pelajaran ini, Anda bisa

  • Memutuskan apakah sistem cukup menjadi modular monolith atau perlu dipecah, dan menemukan batasnya dari bahasa serta kepemilikan data.
  • Menghitung dampak rantai panggilan sinkron terhadap availability, lalu merancang ulang dengan 202 Accepted, queue, dan worker.
  • Memilih antara queue dan pub/sub, serta antara reverse proxy, API gateway, dan service mesh.
Lihat cara kerjanya

Ikuti diagramnya langkah demi langkah

Tekan Putar, atau melangkah dengan panah. Setiap langkah juga ditulis di bawah diagram.

Rantai sinkron versus queue dan 202

Empat service yang dipanggil berantai hanya seandal gabungan keempatnya; di balik 202 Accepted, queue, dan worker, pekerjaan yang sama tetap selamat saat satu dependensi mati.

Langkah 1 dari 7. Checkout berjalan lewat sebuah rantai: API memanggil orders, orders memanggil payment, payment memanggil bank. Anggap masing-masing hidup 99,9 persen dari waktu dan gagal secara independen.

Tombol: ← → langkah · Home / End · K putar atau jeda

  1. Checkout berjalan lewat sebuah rantai: API memanggil orders, orders memanggil payment, payment memanggil bank. Anggap masing-masing hidup 99,9 persen dari waktu dan gagal secara independen.

  2. Secara sinkron, setiap service menunggu jawaban service berikutnya sebelum bisa membalas. Request pengguna tetap terbuka sampai bank, empat hop jauhnya, menjawab.

    Tebak langkah berikutnya Tiap service hidup 99,9%. Berapa availability seluruh rantai? (Tetap 99,9 persen · Sekitar 99,6 persen · Sekitar 96 persen)
  3. Rantai hanya berjalan jika keempatnya berjalan: 0,999 pangkat empat sekitar 99,6 persen, kira-kira empat kali downtime satu service.

    0.999 ** 4 // 0.996006
  4. API bank berhenti menjawab. Payment menunggu, orders menunggu, API menunggu, dan pengguna mendapat timeout, padahal tiga dari empat service sehat.

  5. Desain ulang: API memeriksa order, menulis job ke queue, lalu membalas 202 Accepted beserta URL status. Kini pengguna hanya menunggu dua hop, bukan empat.

    Tebak langkah berikutnya Bank masih mati. Apa yang terjadi pada job itu? (Job itu hilang · Job menunggu di queue · Order dibatalkan)
  6. Worker mengambil job dari queue. Selama bank mati, setiap percobaan gagal, jadi job tetap di queue dan diulang dengan backoff. Tidak ada yang hilang.

  7. Saat bank pulih, worker menjalankan rantai yang sama dan job berhasil. Klien memeriksa URL status dan melihat paid; 202 hanya menjanjikan bahwa request diterima.

Kapan monolith perlu dipecah menjadi microservice?

Lebih lambat dari dugaan banyak tim. Batas service berharga karena menyimpan kode dan data satu kemampuan bisnis di balik antarmuka kecil, dan dinding itu bisa dibangun di dalam satu deployable. Di modular monolith, setiap modul memiliki tabelnya sendiri, membuka facade yang sempit, dan build gagal ketika modul lain menerobosnya. Argumen monolith-first dari Martin Fowler berangkat dari betapa sulitnya menentukan batas yang tepat di awal, dan betapa lebih sulit lagi memindahkannya setelah ada jaringan di antaranya.

Baca selengkapnya (1)

Keluarkan sebuah modul hanya jika Anda bisa menyebut kendala yang diselesaikannya: modul itu harus di-scale berbeda dari yang lain, tim lain butuh jadwal rilis sendiri, modul itu butuh runtime berbeda, atau ada aturan yang menuntut isolasi ketat. Kode yang sekadar terasa besar tidak termasuk, karena masalah ukuran sudah diselesaikan oleh modul. Memecah sistem yang kusut hanya mengubah pemanggilan fungsi yang kusut menjadi pemanggilan jaringan yang kusut.

Bagaimana menemukan di mana satu service harus berakhir?

Dengarkan bahasanya. Domain-driven design menyebut area tempat satu model dan satu kosakata tetap konsisten sebagai bounded context. Ketika gudang, penjualan, dan akuntansi sama-sama menyebut item tetapi maksudnya berbeda, Anda sudah menemukan tiga context, dan memaksakannya ke satu tabel berarti setiap perubahan harus melewati tiga tim. Eric Evans, yang dikutip Fowler, mengatakannya terus terang: menyatukan seluruh model sistem besar tidak layak dan tidak sepadan biayanya.

Baca selengkapnya (1)

Di dalam setiap context, jauhkan aturan bisnis dari database dan framework. Hexagonal architecture, disebut juga ports and adapters, melakukannya dengan membiarkan inti aplikasi mendefinisikan port, yaitu antarmuka dalam bahasanya sendiri seperti simpan order, lalu memasang adapter ke port itu: repository Postgres di production, repository in-memory saat testing. Modul yang dibangun seperti ini kelak bisa dipindah ke balik jaringan tanpa aturan bisnisnya ikut terganggu.

Sebaiknya service saling memanggil secara sinkron atau lewat queue?

Tanyakan pada setiap panggilan: apakah pemanggil butuh jawabannya untuk melanjutkan? Jika ya, panggilan HTTP atau gRPC sinkron adalah pilihan paling sederhana, tetapi kedua service jadi terikat waktu: keduanya harus hidup pada saat yang sama. Di sepanjang rantai, availability dikalikan. Empat hop wajib masing-masing 99,9 persen menghasilkan 0,999 pangkat empat, sekitar 99,6 persen, dan latency bertambah dengan cara yang sama, jadi hop paling lambat menentukan batas bawah setiap request.

Baca selengkapnya (2)

Jika pemanggil cukup tahu bahwa pekerjaannya diterima, gunakan pola 202. API memvalidasi request, menyimpan job ke queue, lalu menjawab 202 Accepted beserta tautan ke resource status. RFC 9110 menyebut 202 sengaja tidak berkomitmen: artinya request sudah diterima, bukan pasti berhasil, jadi resource status harus bisa melaporkan kegagalan. Worker menguras queue dengan kecepatannya sendiri dan mengulang selama dependensinya mati. Pengiriman menjadi at-least-once, jadi worker harus idempotent, dan queue itu sendiri kini berada di jalur pengguna.

Lalu tentukan siapa yang menerima setiap pesan. Queue memberikan setiap pesan ke tepat satu worker, jadi menambah worker membuat antrean cepat habis; cocok untuk pekerjaan yang harus terjadi sekali, seperti mengirim struk. Pub/sub memberikan setiap pesan ke semua subscriber, jadi beberapa service bisa bereaksi pada satu event. Redis Pub/Sub mengirim at-most-once, dan subscriber yang sedang terputus saat pesan dipublikasikan tidak akan pernah melihatnya. Stream yang tahan lama dengan satu consumer group per service memberi keduanya: setiap group melihat semua pesan, dan di dalam satu group setiap pesan hanya ke satu consumer.

Perangkat apa yang dibutuhkan service terpisah, dan bagaimana migrasinya?

Proses yang terpisah harus bisa saling menemukan. Docker memberi alamat baru setiap kali container dijalankan, jadi panggil service dengan namanya: Docker Compose mendaftarkan setiap nama service ke DNS internal. Trafik dari luar, disebut north-south, masuk lewat satu pintu depan, yaitu API gateway atau reverse proxy biasa yang menangani TLS, autentikasi, dan rate limit. Trafik antar-service, east-west, adalah wilayah service mesh, dengan proxy yang menambahkan mutual TLS, retry, dan telemetri. Tim kecil dengan satu server butuh pintu depan itu, dan jarang butuh mesh.

Baca selengkapnya (1)

Untuk memindahkan monolith yang sudah ada, jangan menulis ulang. Strangler fig pattern memasang facade di depan sistem lama, mengalihkan satu path demi satu path ke service baru, dan menyiapkan rute lama untuk rollback. Data adalah bagian tersulit: selama service baru belum memiliki tabelnya sendiri, ia hanyalah distributed monolith dengan hop jaringan tambahan. Berhentilah saat sisanya sudah stabil dan murah dijalankan; modular monolith yang terawat adalah titik akhir yang sah.

Cara situs ini melakukannya

Konfigurasi nyata di balik pelajaran ini

Kutipan dari repositori yang membangun dan men-deploy situs ini, apa adanya.

app/api/contact/route.ts:137-153
  try {
    const { error } = await getResend(apiKey).emails.send({
      from: fromEmail,
      to: toEmail,
      replyTo: email,
      template: {
        id: isHiring ? TEMPLATE_HIRING : TEMPLATE_PROJECT,
        variables,
      },
    });

    if (error) {
      console.error("Resend send error:", error);
      return apiError({
        status: 502,
        code: "upstream_error",
        message: "Failed to send message.",

Situs ini satu aplikasi Next.js, bukan kumpulan service, tetapi form kontaknya punya dependensi sinkron persis seperti yang dibahas di sini. Route ini menunggu penyedia email di dalam request pengunjung, jadi jika penyedianya gagal, pengunjung mendapat 502 dan harus mencoba lagi. Dengan lima pesan per menit per IP dan tanpa broker yang berjalan, itu trade-off yang wajar; queue dan 202 akan membuat pesannya menunggu, bukan gagal.

lib/api/versioning.ts:50-58
/**
 * The unversioned paths the site used before v1. They still work (cached
 * pages and old clients call them), but are deprecated in favour of v1.
 */
export const DEPRECATED_API_ALIASES = [
  { path: "/api/chat", successor: API_PATHS.chat },
  { path: "/api/chat/followups", successor: API_PATHS.followups },
  { path: "/api/contact", successor: API_PATHS.contact },
] as const;

Kontrak sebuah batas berubah dengan cara strangler: alihkan rutenya dulu, pindahkan kodenya belakangan. Path lama tanpa versi tetap menjawab, next.config.ts menempelkan header Deprecation dan Link ke penggantinya di v1, dan app/api/v1/contact/route.ts hanya mengekspor ulang handler lama. Klien pindah dengan kecepatannya sendiri, dan tidak ada yang rusak pada hari perubahan.

Yang sering rusak

Kesalahan yang benar-benar membuang waktu

  • Distributed monolith

    Service yang masih membaca dan menulis tabel milik service lain lolos semua uji routing, tetapi tetap menghambat setiap perubahan skema, karena setiap perubahan butuh semua tim. Berikan satu pemilik untuk setiap tabel, dan biarkan yang lain mengakses datanya hanya lewat API atau event milik pemilik itu.

  • Retry di setiap lapisan

    Jika tiga lapisan masing-masing mencoba tiga kali, service paling bawah yang sedang kesulitan bisa menerima 27 panggilan untuk satu aksi pengguna. Lakukan retry di satu lapisan saja, dalam satu deadline, dan hanya untuk operasi yang aman diulang: POST yang menagih kartu tidak aman, kecuali membawa idempotency key.

  • Publish setelah commit

    Commit order, lalu publish event, dan crash di antara keduanya membuat data berubah tanpa ada yang diberi tahu. Tulis event ke tabel outbox dalam transaksi yang sama, lalu biarkan relay yang mempublikasikannya. Relay bisa mempublikasikan dua kali, jadi consumer harus mengabaikan duplikat.

  • 202 yang tidak bisa ditepati

    Membalas 202 sebelum job tersimpan dengan aman, atau tanpa resource status, membuat klien tidak bisa mengetahui hasilnya. RFC 9110 mencatat bahwa HTTP tidak punya cara mengirim ulang status code belakangan, jadi resource status adalah satu-satunya jalur, dan harus bisa menyatakan gagal.

Panggilan sinkron atau 202 dengan queue
Pengguna menungguSetiap hop di rantaiAPI dan penulisan ke queue
Dependensi matiRequest gagal atau timeoutJob menunggu lalu diulang
Arti balasannyaPekerjaan sudah selesaiPekerjaan sudah diterima
Komponen tambahanTidak ada selain jaringanBroker, worker, dan resource status
Cocok untukQuery dan jawaban yang dibutuhkan saat ituPerintah lambat dan efek samping
Coba sendiri

Lakukan di komputer Anda sendiri

Anda hanya butuh Node dan Docker. Hitung dulu aritmetika rantainya, lalu lihat perbedaan perilaku queue dan pub/sub di Redis lokal.

  1. Cetak availability rantai berisi 1, 2, 4, dan 8 service yang masing-masing 99,9 persen. Hasilnya 99.90%, 99.80%, 99.60%, dan 99.20%.

    node -e 'for (const n of [1, 2, 4, 8]) console.log(n, (100 * 0.999 ** n).toFixed(2) + "%")'
  2. Jalankan Redis, lalu buka dua terminal sebagai worker yang bersaing, keduanya menunggu di list yang sama.

    docker run -d --name lab-redis redis:7-alpine
    
    # terminal A, and the same in terminal B
    docker exec -it lab-redis redis-cli BRPOP jobs 0
  3. Dari terminal ketiga, masukkan dua job. Setiap worker mencetak satu job, tidak pernah keduanya: queue memberikan setiap pesan ke satu consumer.

    docker exec lab-redis redis-cli LPUSH jobs receipt:41
    docker exec lab-redis redis-cli LPUSH jobs receipt:42
  4. Sekarang buat terminal A dan B subscribe ke satu channel lalu publish: keduanya mencetak pesan itu, dan PUBLISH melaporkan 2 penerima. Hentikan B dengan Ctrl-C, publish lagi, jalankan B kembali: pesan itu tidak pernah sampai.

    # terminals A and B
    docker exec -it lab-redis redis-cli SUBSCRIBE orders
    
    # third terminal
    docker exec lab-redis redis-cli PUBLISH orders paid:42
    
    # clean up
    docker rm -f lab-redis
Kuis singkat

Tiga pertanyaan sebelum lanjut

Pilih jawaban untuk melihat kenapa benar atau salah. Tidak ada yang disimpan atau dinilai, jadi tebak saja dengan bebas.

  1. 1.Checkout Anda memanggil stock, payment, dan email secara sinkron, masing-masing 99,9 persen. Penyedia email mati selama satu jam. Apa yang dilihat pelanggan?
  2. 2.Anda memindahkan ekspor laporan yang lambat ke balik 202 Accepted, queue, dan worker. Klien menerima 202. Apa yang dijanjikan server?
  3. 3.Tiga service masing-masing harus bereaksi saat order dibayar, dan tidak boleh ada yang melewatkan event saat sedang restart. Mana yang cocok?
Pikirkan dulu

Jawab dalam hati, lalu cek

  1. 1.Sebuah request melewati lima service, masing-masing 99,9 persen. Kira-kira berapa availability rantainya?Lihat jawabannya

    Jawaban:Sekitar 99,5 persen, karena 0,999 pangkat lima adalah 0,995. Itu kira-kira lima kali downtime satu service, dengan asumsi service-service itu gagal secara independen.

  2. 2.Kenapa worker yang membaca dari queue harus idempotent?Lihat jawabannya

    Jawaban:Karena pengirimannya at-least-once. Job bisa dikirim ulang setelah crash atau timeout, jadi memprosesnya dua kali tidak boleh menagih atau mengirim email dua kali.

  3. 3.Codebase Anda terasa terlalu besar. Apakah itu alasan untuk memecahnya menjadi microservice?Lihat jawabannya

    Jawaban:Tidak, jika hanya itu alasannya. Masalah ukuran diselesaikan oleh modul dengan batas yang ditegakkan. Pecahlah jika Anda bisa menyebut kendalanya: profil scaling berbeda, jadwal rilis mandiri, runtime lain, atau aturan isolasi ketat.

Baca modulnya

Semua artikel di modul ini, berurutan

Pelajaran di atas adalah petanya. Setiap artikel membahas satu bagiannya secara mendalam, lengkap dengan contoh.

  1. Arsitektur Modular Monolith: Kapan Mengalahkan MicroservicesApa itu modular monolith dan kapan lebih baik dari microservices: satu deployable, public API per module, schema Postgres per module, dan boundary yang dijaga lint.
  2. Bounded Context di Domain-Driven Design dan Cara MenemukannyaApa itu bounded context, mengapa Item punya tiga makna di sales, inventory dan accounting ERP, serta cara menemukan batasnya dan menyambungkannya lewat context map.
  3. Hexagonal Architecture (Ports and Adapters) di TypeScriptApa itu hexagonal architecture, bagaimana ports dan adapters menjaga business logic bebas dari framework dan database, plus contoh NestJS dan Postgres.
  4. Komunikasi Sync vs Async Microservices: Cara MemilihKapan service sebaiknya memanggil service lain lalu menunggu, dan kapan cukup publish message: hitungan availability, latency, penanganan failure, consistency, dan hybrid 202 Accepted.
  5. Pub/Sub vs Message Queue: Perbedaan dan Kapan MemakainyaPub/sub vs message queue: competing consumers versus fan-out, delivery guarantee, ordering, replay, consumer group, plus checklist untuk job, notifikasi dan event.
  6. Service Discovery di Microservices: Cara KerjanyaBagaimana service discovery bekerja di microservices? Registry, heartbeat, TTL, client-side vs server-side, DNS di Docker Compose dan Kubernetes, Consul.
  7. Service Mesh vs API Gateway: Perbedaan dan Kapan MemakainyaService mesh vs API gateway: traffic north-south dan east-west, sidecar vs ambient mesh, config nginx dan Istio asli, biaya, serta kapan Anda tidak butuh keduanya.
  8. Strangler Fig Pattern: Migrasi Monolith ke MicroservicesCara migrasi monolith ke microservices dengan strangler fig pattern: facade routing, satu slice per langkah, kepemilikan data, rollback, dan kapan berhenti.