API Gateway (REST) ​​+ Layanan-Driven Microservices

16

Saya memiliki banyak layanan microser yang fungsinya saya paparkan melalui REST API sesuai dengan pola Gateway API. Karena layanan microservice ini adalah aplikasi Spring Boot, saya menggunakan Spring AMQP untuk mencapai komunikasi sinkron gaya RPC antara layanan microser ini. Sejauh ini segalanya berjalan mulus. Namun, semakin saya membaca tentang arsitektur layanan acara-mikro dan melihat proyek-proyek seperti Spring Cloud Stream semakin saya yakin bahwa saya mungkin melakukan hal-hal yang salah dengan RPC, pendekatan sinkron (terutama karena saya akan membutuhkan ini untuk skala untuk menanggapi ratusan atau ribuan permintaan per detik dari aplikasi klien).

Saya mengerti poin di balik arsitektur yang digerakkan oleh peristiwa. Apa yang saya tidak mengerti adalah bagaimana sebenarnya menggunakan pola seperti itu ketika duduk di belakang model (REST) ​​yang mengharapkan respons untuk setiap permintaan. Misalnya, jika saya memiliki gateway API sebagai layanan mikro dan layanan mikro lain yang menyimpan dan mengelola pengguna, bagaimana saya bisa memodelkan sesuatu seperti dengan cara GET /users/1yang murni didorong oleh peristiwa?

Tony E. Stark
sumber

Jawaban:

9

Ulangi setelah saya:

REST dan acara asinkron bukanlah alternatif. Mereka sepenuhnya ortogonal.

Anda dapat memiliki satu, atau yang lain, atau keduanya, atau tidak sama sekali. Mereka alat yang sama sekali berbeda untuk domain masalah yang sama sekali berbeda. Faktanya, komunikasi permintaan-respons untuk keperluan umum benar-benar mampu menjadi tidak sinkron, berdasarkan peristiwa, dan toleran terhadap kesalahan .


Sebagai contoh sepele, protokol AMQP mengirim pesan melalui koneksi TCP. Dalam TCP, setiap paket harus diakui oleh penerima . Jika pengirim paket tidak menerima ACK untuk paket itu, ia terus mengirim ulang paket itu sampai ACK atau sampai lapisan aplikasi "menyerah" dan meninggalkan koneksi. Ini jelas merupakan model permintaan-respons non-toleran karena setiap "permintaan pengiriman paket" harus memiliki "paket respons respons" yang menyertainya, dan kegagalan untuk merespons mengakibatkan seluruh koneksi gagal. Namun AMQP, protokol standar dan diadopsi secara luas untuk pesan toleransi kesalahan asinkron, dikomunikasikan melalui TCP! Apa yang menyebabkannya?

Konsep inti yang dimainkan di sini adalah bahwa pesan yang toleran-bebas-skal yang dapat diskalakan ditentukan oleh pesan apa yang Anda kirim , bukan bagaimana Anda mengirimnya . Dengan kata lain, kopling longgar didefinisikan pada lapisan aplikasi .

Mari kita lihat dua pihak yang berkomunikasi baik secara langsung dengan RESTful HTTP atau secara tidak langsung dengan broker pesan AMQP. Misalkan Pihak A ingin mengunggah gambar JPEG ke Pihak B yang akan menajamkan, mengompres, atau meningkatkan gambar. Pihak A tidak memerlukan gambar yang diproses segera, tetapi memang membutuhkan referensi untuk itu untuk penggunaan dan pengambilan di masa depan. Inilah salah satu cara yang mungkin berlaku di REST:

  • Pihak A mengirim POSTpesan permintaan HTTP ke Pihak B denganContent-Type: image/jpeg
  • Pihak B memproses gambar (untuk waktu yang lama jika itu besar) sementara Pihak A menunggu, mungkin melakukan hal-hal lain
  • Pihak B mengirim 201 Createdpesan tanggapan HTTP ke Pihak A dengan Content-Location: <url>tajuk yang menautkan ke gambar yang diproses
  • Pihak A menganggap pekerjaannya dilakukan karena sekarang memiliki referensi ke gambar yang diproses
  • Suatu saat di masa depan ketika Pihak A membutuhkan gambar yang diproses, itu MENDAPATKANnya menggunakan tautan dari Content-Locationheader sebelumnya

The 201 Createdkode respon memberitahu klien bahwa tidak hanya permintaan mereka berhasil, itu juga menciptakan sumber daya baru. Dalam respons 201, Content-Locationheader adalah tautan ke sumber daya yang dibuat. Ini ditentukan dalam RFC 7231 Bagian 6.3.2 dan 3.1.4.2.

Sekarang, mari kita lihat bagaimana interaksi ini bekerja melalui protokol RPC hipotetis di atas AMQP:

  • Pihak A mengirim broker pesan AMQP (sebut saja Messenger) pesan yang berisi gambar dan instruksi untuk merutekannya ke Pihak B untuk diproses, kemudian menanggapi Pihak A dengan alamat semacam alamat untuk gambar.
  • Pihak A menunggu, mungkin melakukan hal-hal lain
  • Messenger mengirimkan pesan asli Pihak A ke Pihak B
  • Pihak B memproses pesan tersebut
  • Pihak B mengirim pesan kepada Messenger yang berisi alamat untuk gambar yang diproses dan instruksi untuk merutekan pesan itu ke Pihak A
  • Messenger mengirim Pihak A pesan dari Pihak B yang berisi alamat gambar yang diproses
  • Pihak A menganggap pekerjaannya dilakukan karena sekarang memiliki referensi ke gambar yang diproses
  • Suatu saat di masa depan ketika Pihak A membutuhkan gambar, itu mengambil gambar menggunakan alamat (mungkin dengan mengirim pesan ke pihak lain)

Apakah Anda melihat masalahnya di sini? Dalam kedua kasus, Pihak A tidak bisa mendapatkan alamat gambar sampai setelah Pihak B memproses gambar . Namun Pihak A tidak membutuhkan gambar segera dan, dengan semua hak, tidak peduli jika pemrosesan selesai!

Kami dapat memperbaiki ini dengan mudah dalam kasus AMQP dengan meminta Pihak B memberi tahu A bahwa B menerima gambar untuk diproses, memberikan A alamat tempat gambar akan berada setelah pemrosesan selesai. Kemudian Pihak B dapat mengirim pesan pada suatu waktu di masa depan yang mengindikasikan pemrosesan gambar selesai. Olahpesan AMQP untuk menyelamatkan!

Kecuali tebak: Anda dapat mencapai hal yang sama dengan REST . Dalam contoh AMQP kami mengubah pesan "inilah gambar yang diproses" menjadi "gambar sedang diproses, Anda bisa mendapatkannya nanti". Untuk melakukannya di RESTful HTTP, kami akan menggunakan 202 Acceptedkode dan Content-Locationlagi:

  • Pihak A mengirim POSTpesan HTTP ke Pihak B denganContent-Type: image/jpeg
  • Pihak B segera mengirim kembali 202 Acceptedrespons yang berisi semacam konten "operasi asinkron" yang menjelaskan apakah pemrosesan selesai dan di mana gambar akan tersedia ketika selesai diproses. Termasuk juga Content-Location: <link>tajuk yang, dalam 202 Acceptedrespons, adalah tautan ke sumber daya yang diwakili oleh apa pun badan respons. Dalam hal ini, itu artinya itu adalah tautan ke operasi asinkron kami!
  • Pihak A menganggap pekerjaannya dilakukan karena sekarang memiliki referensi ke gambar yang diproses
  • Suatu saat di masa depan ketika Pihak A membutuhkan gambar yang diproses, pertama-tama MENDAPATKAN sumber daya operasi async yang ditautkan ke dalam Content-Locationheader untuk menentukan apakah pemrosesan selesai. Jika demikian, Pihak A kemudian menggunakan tautan dalam operasi async itu sendiri untuk MENDAPATKAN gambar yang diproses.

Satu-satunya perbedaan di sini adalah bahwa dalam model AMQP, Pihak B memberi tahu Pihak A ketika pemrosesan gambar dilakukan. Tetapi dalam model REST, Pihak A memeriksa apakah pemrosesan dilakukan sebelum benar-benar membutuhkan gambar. Pendekatan-pendekatan ini dapat diukur secara ekuivalen . Ketika sistem semakin besar, jumlah pesan yang dikirim baik dalam async AMQP dan asest strategi REST meningkat dengan kompleksitas asimptotik yang setara. Satu-satunya perbedaan adalah klien mengirim pesan tambahan, bukan server.

Tetapi pendekatan REST memiliki beberapa trik lagi: penemuan dinamis dan negosiasi protokol . Pertimbangkan bagaimana interaksi REST sinkronisasi dan async dimulai. Pihak A mengirim permintaan yang sama persis kepada Pihak B, dengan satu-satunya perbedaan adalah jenis pesan keberhasilan tertentu yang direspon oleh Pihak B. Bagaimana jika Pihak A ingin memilih apakah pemrosesan gambar sinkron atau asinkron? Bagaimana jika Pihak A tidak tahu apakah Pihak B bahkan mampu memproses async?

Nah, HTTP sebenarnya sudah memiliki protokol standar untuk ini! Ini disebut HTTP Preferences, khususnya respond-asyncpreferensi RFC 7240 Bagian 4.1. Jika Pihak A menginginkan respons yang tidak sinkron, ia menyertakan Prefer: respond-asyncheader dengan permintaan POST awalnya. Jika Pihak B memutuskan untuk menghormati permintaan ini, ia mengirim kembali 202 Acceptedrespons yang mencakup a Preference-Applied: respond-async. Jika tidak, Pihak B mengabaikan Preferheader dan mengirim kembali 201 Createdseperti biasanya.

Hal ini memungkinkan Pihak A untuk bernegosiasi dengan server, secara dinamis beradaptasi dengan implementasi pemrosesan gambar apa pun yang kebetulan sedang diajaknya bicara. Lebih lanjut, penggunaan tautan eksplisit berarti Pihak A tidak perlu tahu tentang pihak lain selain B: tidak ada broker pesan AMQP, tidak ada Pihak C misterius yang tahu bagaimana mengubah alamat gambar menjadi data gambar, tanpa B-Async kedua pihak jika permintaan sinkron dan asinkron perlu dibuat, dll. Ini hanya menggambarkan apa yang dibutuhkan, apa yang secara opsional suka, dan kemudian bereaksi terhadap kode status, konten respons, dan tautan. MenambahkanCache-Controlheader untuk instruksi eksplisit tentang kapan menyimpan salinan data lokal, dan sekarang server dapat bernegosiasi dengan klien yang sumber dayanya dapat disimpan oleh salinan lokal (atau bahkan offline!). Ini adalah bagaimana Anda membangun microservices toleran-longgar yang digabungkan dalam REST.

Mendongkrak
sumber
1

Apakah Anda perlu murni didorong oleh peristiwa atau tidak, tentu saja, pada skenario spesifik Anda. Dengan anggapan Anda memang perlu, maka Anda bisa menyelesaikan masalah dengan:

Menyimpan salinan data read-only dengan mendengarkan berbagai peristiwa dan menangkap informasi dalam muatan mereka. Sementara ini memberi Anda membaca cepat untuk data itu, disimpan dalam bentuk yang sesuai dengan aplikasi yang tepat, itu juga berarti data Anda pada akhirnya akan konsisten di seluruh layanan.

Untuk membuat model GET /users/1dengan pendekatan ini, orang mungkin mendengarkan UserCreateddan UserUpdatedacara, dan menyimpan subset berguna dari data pengguna dalam layanan. Saat Anda perlu mendapatkan informasi pengguna itu, Anda dapat dengan mudah meminta penyimpanan data lokal Anda.

Untuk sesaat, mari kita asumsikan bahwa layanan yang mengekspos /users/titik akhir tidak mempublikasikan segala macam peristiwa. Dalam hal ini, Anda bisa mencapai hal yang sama dengan hanya menembolok respons terhadap permintaan HTTP yang Anda buat, sehingga meniadakan kebutuhan untuk membuat lebih dari 1 permintaan HTTP per pengguna dalam jangka waktu tertentu.

Andy Hunt
sumber
Saya mengerti. Tetapi bagaimana dengan penanganan kesalahan (dan pelaporan) kepada klien dalam skenario ini?
Tony E. Stark
Maksud saya, bagaimana cara melaporkan kembali ke REST klien kesalahan yang terjadi saat menangani UserCreatedacara (misalnya, duplikat nama pengguna atau email atau pemadaman database).
Tony E. Stark
Itu tergantung di mana Anda melakukan tindakan. Jika Anda berada di dalam sistem pengguna, Anda dapat melakukan semua validasi, menulis ke penyimpanan data di sana, lalu menerbitkan acara tersebut. Kalau tidak, saya melihatnya sangat dapat diterima untuk melakukan permintaan HTTP standar ke /users/titik akhir, dan memungkinkan sistem untuk menerbitkan acara jika berhasil, dan menanggapi permintaan dengan entitas baru
Andy Hunt
0

Dengan sistem sumber peristiwa, aspek asinkron biasanya ikut bermain ketika sesuatu yang mewakili keadaan, mungkin database, atau tampilan agregat dari beberapa data, diubah. Dengan menggunakan contoh Anda, panggilan ke GET / api / pengguna dapat dengan mudah mengembalikan respons dari layanan yang memiliki representasi terkini dari daftar pengguna dalam sistem. Dalam skenario lain, permintaan untuk GET / api / pengguna dapat menyebabkan layanan menggunakan aliran peristiwa sejak snapshot terakhir pengguna untuk membuat snapshot lain dan hanya mengembalikan hasilnya. Sistem yang digerakkan oleh peristiwa tidak harus murni tidak sinkron dari Permintaan ke Respons, tetapi cenderung berada pada tingkat di mana layanan perlu berinteraksi dengan layanan lain. Seringkali tidak masuk akal untuk secara asinkron mengembalikan permintaan GET dan oleh karena itu Anda hanya dapat mengembalikan respons suatu layanan,

Lloyd Moore
sumber