Setelah batas service terpasang, service-service harus saling berbicara — dan dua gaya besar yang tersedia punya karakter yang bertolak belakang. Gaya sinkron (REST, gRPC) bekerja seperti menelepon: pemanggil mengirim permintaan lalu terblokir menunggu jawaban sebelum bisa melanjutkan apa pun. Gaya asinkron (messaging) bekerja seperti mengirim surat ke kotak pos: penerbit menitipkan pesan lalu langsung bergerak, dan penerima membacanya kapan pun ia siap. Pilihan di antara keduanya menentukan latensi yang dirasakan pengguna, seberapa rapat service-service terikat, dan bagaimana kegagalan menjalar. Chapter ini menjalankan alur pemesanan yang sama pada kedua gaya lalu memetakan konsekuensinya.
Telepon dan Kotak Surat #
Analogi telepon dan surat membantu selama batasnya kita pahami. Pada REST sinkron, klien dan server harus hidup pada saat yang sama — bila svc-pembayaran sedang tidak berjalan, panggilan langsung gagal; kopling semacam ini disebut temporal coupling (kopling waktu). Pada messaging, penerbit dan pelanggan tidak pernah berhubungan langsung — penerbit menitipkan event ke antrean, dan event tersebut menunggu sampai pelanggan siap. Itulah sebabnya sistem messaging tetap bekerja ketika salah satu pelanggan sedang berhenti: pesannya antre.
REST Berantai yang Menunggu #
Program berikut membandingkan kedua gaya dengan satuan “langkah kerja” — bukan milidetik — agar perhitungannya tetap deterministik. Sisi sinkron memodelkan klien yang memanggil svc-order, yang kemudian memanggil dua service lain secara berantai:
import java.util.ArrayDeque;
import java.util.Deque;
import java.util.List;
/** Alur pemesanan yang sama pada dua gaya komunikasi: REST sinkron vs messaging asinkron. */
public class SinkronVsAsinkron {
/** Satu langkah kerja: pelakunya, aksinya, dan bobot kerjanya. */
record Pekerjaan(String pelaku, String aksi, int langkah) {}
public static void main(String[] args) {
System.out.println("== SINKRON: REST berantai, klien menunggu ==");
List<Pekerjaan> rantai = List.of(
new Pekerjaan("klien", "kirim POST ke svc-order", 0),
new Pekerjaan("svc-order", "validasi pesanan", 1),
new Pekerjaan("svc-pembayaran", "menagih kartu", 3),
new Pekerjaan("svc-notifikasi", "menulis email", 2));
int menungguSinkron = 0;
for (Pekerjaan p : rantai) {
System.out.println(" " + String.format("%-15s", p.pelaku()) + ": " + p.aksi()
+ " (" + p.langkah() + " langkah)");
menungguSinkron += p.langkah();
}
System.out.println(" -> klien dijawab setelah " + menungguSinkron + " langkah kerja");
System.out.println();
System.out.println("== ASINKRON: messaging lewat antrean ==");
// svc-order memvalidasi, menjawab klien, lalu mempublikasikan satu event
System.out.println(" svc-order : validasi pesanan (1 langkah), menjawab klien");
Deque<String> antrean = new ArrayDeque<>();
antrean.offer("event PesananDibuat#1001");
// Penerbit tidak tahu siapa pelanggannya; antrean yang menyebarkan
List<String> pelanggan = List.of("svc-pembayaran", "svc-notifikasi");
int kerjaBelakangan = 0;
while (!antrean.isEmpty()) {
String event = antrean.poll();
for (String p : pelanggan) {
int langkah = p.equals("svc-pembayaran") ? 3 : 2;
kerjaBelakangan += langkah;
System.out.println(" antrean -> " + String.format("%-15s", p) + ": "
+ event + " diproses (" + langkah + " langkah, klien tidak menunggu)");
}
}
System.out.println(" -> klien dijawab setelah 1 langkah; "
+ kerjaBelakangan + " langkah berjalan setelah jawaban terkirim");
}
}Output yang Dihasilkan:
== SINKRON: REST berantai, klien menunggu ==
klien : kirim POST ke svc-order (0 langkah)
svc-order : validasi pesanan (1 langkah)
svc-pembayaran : menagih kartu (3 langkah)
svc-notifikasi : menulis email (2 langkah)
-> klien dijawab setelah 6 langkah kerja
== ASINKRON: messaging lewat antrean ==
svc-order : validasi pesanan (1 langkah), menjawab klien
antrean -> svc-pembayaran : event PesananDibuat#1001 diproses (3 langkah, klien tidak menunggu)
antrean -> svc-notifikasi : event PesananDibuat#1001 diproses (2 langkah, klien tidak menunggu)
-> klien dijawab setelah 1 langkah; 5 langkah berjalan setelah jawaban terkirimMari kita bedah kedua sisinya:
record Pekerjaan(pelaku, aksi, langkah): setiap pekerjaan diberi bobotlangkah(0 untuk aksi kirim murni). Sisi sinkron menjumlahkan bobot seluruh rantai menjadimenungguSinkron— klien dijawab setelah 6 langkah karena notifikasi email pun ikut ditunggu.- Rantai
List.of(...): urutan list adalah urutan eksekusi.svc-ordermemanggilsvc-pembayaranlalusvc-notifikasi, dan klien paling luar menanggung jumlah keduanya — latensi pada gaya sinkron bersifat aditif sepanjang rantai. Deque<String> antrean+offer(...): sisi asinkron memakaiArrayDequesebagai antrean pesan sungguhan (FIFO —offermenambah di ekor,pollmengambil dari kepala).svc-ordermemvalidasi, menjawab klien, dan baru mempublikasikanevent PesananDibuat#1001.List<String> pelanggan: daftar pelanggan tidak diketahuisvc-order— penerbit hanya berbicara ke antrean. Loopwhilememindahkan satu event dari antrean dan menyebarkannya ke setiap pelanggan, masing-masing dengan bobot kerjanya sendiri.- Baris penutup kedua sisi: klien asinkron dijawab setelah 1 langkah; 5 langkah sisanya berjalan setelah jawaban terkirim. Angka 5 itu bukan berubah jadi nol — pekerjaannya tetap ada, hanya dipindahkan keluar dari jalur tunggu pengguna.
Konsekuensi Kedua Gaya #
Perbandingan lengkapnya lebih luas dari latensi saja:
| Aspek | REST sinkron | Messaging asinkron |
|---|---|---|
| Jawaban untuk pemanggil | langsung ada / tidak | tidak ada — hanya konfirmasi pesan diterima |
| Latensi dirasakan pengguna | seluruh rantai ditunggu | hanya pekerjaan penerbit |
| Kopling waktu | kedua pihak harus hidup bersamaan | pelanggan boleh menyusul |
| Kegagalan | menjalar seketika ke pemanggil | tersembunyi sampai pelanggan memroses |
| Buffer saat pelanggan kewalahan | tidak ada — pemanggil menunggu atau gagal | antrean menampung dan melancarkan beban |
| Konsistensi data | cenderung kuat | konsistensi eventual |
| Cocok untuk | validasi, kueri, keputusan | pekerjaan lanjutan, notifikasi, integrasi |
Baris “kegagalan” dan “konsistensi” adalah harga tersembunyi messaging: bila pelanggan gagal memroses event, penerbit tidak akan pernah tahu dari jalur komunikasi itu sendiri. Broker menjawabnya dengan pengiriman ulang (at-least-once) — yang lalu menimbulkan pertanyaan baru: bagaimana memroses event yang sama dua kali tanpa efek ganda? Pertanyaan itu kita jawab tuntas pada chapter Idempotency.
Memilih dan Menggabungkan #
Aturan pratiknya: bila pengguna membutuhkan jawaban untuk melanjutkan, pakai sinkron; bila pekerjaannya berupa “setelah ini terjadi, lakukan beberapa hal”, pakai asinkron. Validasi stok saat checkout perlu jawaban sekarang — panggilan REST tepat. Notifikasi email, pencatatan audit, pembaruan layanan rekomendasi tidak perlu menahan pengguna — event lebih tepat.
Sistem nyata hampir selalu memakai keduanya sekaligus: endpoint REST menerima permintaan pengguna, memvalidasi, menjawab, lalu mempublikasikan event untuk seluruh pekerjaan lanjutan. Kerangka pemikiran “jawab yang perlu dijawab, antrekan sisanya” menjadi kerangka yang dipakai sepanjang section ini.
Gaya sinkron yang paling umum di Java adalah REST dengan java.net.http.HttpClient. Chapter berikutnya masuk ke detail praktisnya: cara memanggil service lain, membaca status respons, dan menangani kegagalan jaringan yang pasti datang.
Referensi #
- microservices.io: Pola Messaging
- microservices.io: Pola Remote Procedure Invocation (REST)
- Martin Fowler: Microservices — Smart Endpoints and Dumb Pipes
Berkas Sumber
Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.
SinkronVsAsinkron.java
Unduh berkasimport java.util.ArrayDeque;
import java.util.Deque;
import java.util.List;
/** Alur pemesanan yang sama pada dua gaya komunikasi: REST sinkron vs messaging asinkron. */
public class SinkronVsAsinkron {
/** Satu langkah kerja: pelakunya, aksinya, dan bobot kerjanya. */
record Pekerjaan(String pelaku, String aksi, int langkah) {}
public static void main(String[] args) {
System.out.println("== SINKRON: REST berantai, klien menunggu ==");
List<Pekerjaan> rantai = List.of(
new Pekerjaan("klien", "kirim POST ke svc-order", 0),
new Pekerjaan("svc-order", "validasi pesanan", 1),
new Pekerjaan("svc-pembayaran", "menagih kartu", 3),
new Pekerjaan("svc-notifikasi", "menulis email", 2));
int menungguSinkron = 0;
for (Pekerjaan p : rantai) {
System.out.println(" " + String.format("%-15s", p.pelaku()) + ": " + p.aksi()
+ " (" + p.langkah() + " langkah)");
menungguSinkron += p.langkah();
}
System.out.println(" -> klien dijawab setelah " + menungguSinkron + " langkah kerja");
System.out.println();
System.out.println("== ASINKRON: messaging lewat antrean ==");
// svc-order memvalidasi, menjawab klien, lalu mempublikasikan satu event
System.out.println(" svc-order : validasi pesanan (1 langkah), menjawab klien");
Deque<String> antrean = new ArrayDeque<>();
antrean.offer("event PesananDibuat#1001");
// Penerbit tidak tahu siapa pelanggannya; antrean yang menyebarkan
List<String> pelanggan = List.of("svc-pembayaran", "svc-notifikasi");
int kerjaBelakangan = 0;
while (!antrean.isEmpty()) {
String event = antrean.poll();
for (String p : pelanggan) {
int langkah = p.equals("svc-pembayaran") ? 3 : 2;
kerjaBelakangan += langkah;
System.out.println(" antrean -> " + String.format("%-15s", p) + ": "
+ event + " diproses (" + langkah + " langkah, klien tidak menunggu)");
}
}
System.out.println(" -> klien dijawab setelah 1 langkah; "
+ kerjaBelakangan + " langkah berjalan setelah jawaban terkirim");
}
}