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:

text
== 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 terkirim

Mari kita bedah kedua sisinya:

  1. record Pekerjaan(pelaku, aksi, langkah): setiap pekerjaan diberi bobot langkah (0 untuk aksi kirim murni). Sisi sinkron menjumlahkan bobot seluruh rantai menjadi menungguSinkron — klien dijawab setelah 6 langkah karena notifikasi email pun ikut ditunggu.
  2. Rantai List.of(...): urutan list adalah urutan eksekusi. svc-order memanggil svc-pembayaran lalu svc-notifikasi, dan klien paling luar menanggung jumlah keduanya — latensi pada gaya sinkron bersifat aditif sepanjang rantai.
  3. Deque<String> antrean + offer(...): sisi asinkron memakai ArrayDeque sebagai antrean pesan sungguhan (FIFO — offer menambah di ekor, poll mengambil dari kepala). svc-order memvalidasi, menjawab klien, dan baru mempublikasikan event PesananDibuat#1001.
  4. List<String> pelanggan: daftar pelanggan tidak diketahui svc-order — penerbit hanya berbicara ke antrean. Loop while memindahkan satu event dari antrean dan menyebarkannya ke setiap pelanggan, masing-masing dengan bobot kerjanya sendiri.
  5. 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:

AspekREST sinkronMessaging asinkron
Jawaban untuk pemanggillangsung ada / tidaktidak ada — hanya konfirmasi pesan diterima
Latensi dirasakan penggunaseluruh rantai ditungguhanya pekerjaan penerbit
Kopling waktukedua pihak harus hidup bersamaanpelanggan boleh menyusul
Kegagalanmenjalar seketika ke pemanggiltersembunyi sampai pelanggan memroses
Buffer saat pelanggan kewalahantidak ada — pemanggil menunggu atau gagalantrean menampung dan melancarkan beban
Konsistensi datacenderung kuatkonsistensi eventual
Cocok untukvalidasi, kueri, keputusanpekerjaan 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 #

Berkas Sumber

Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.

SinkronVsAsinkron.java

Unduh berkas
java
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");
    }
}