Bagaimana membuat arsitektur aplikasi web berbasis soket realtime-berat?

17

Dalam proses mengembangkan Aplikasi Halaman Tunggal realtime, saya telah secara progresif mengadopsi websockets untuk memberdayakan pengguna saya dengan data terbaru. Selama fase ini, saya sedih melihat bahwa saya terlalu banyak menghancurkan struktur aplikasi saya, dan saya gagal menemukan solusi untuk fenomena ini.

Sebelum masuk ke spesifik, hanya sedikit konteks:

  • Webapp adalah SPA waktu nyata;
  • Backend ada di Ruby on Rails. Acara realtime didorong oleh Ruby ke kunci Redis, lalu server simpul mikro menariknya kembali dan mendorongnya ke Socket.Io;
  • Frontend berada di AngularJS, dan terhubung langsung ke server socket.io di Node.

Di sisi server, sebelum waktu nyata saya memiliki pemisahan yang jelas berbasis kontroler / model dari sumber daya, dengan pemrosesan yang melekat pada masing-masing. Ini desain MVC klasik benar-benar robek bawah, atau setidaknya dilewati, tepat ketika saya mulai mendorong hal melalui WebSockets untuk pengguna saya. Sekarang saya memiliki satu pipa di mana semua aplikasi saya mengalir lebih atau kurang data terstruktur . Dan saya merasa stres.

Di ujung depan, perhatian utama adalah duplikasi logika bisnis. Ketika pengguna memuat halaman, saya harus memuat model saya melalui panggilan AJAX klasik. Tetapi saya juga harus menangani banjir data real-time, dan saya mendapati diri saya menduplikasi banyak logika bisnis sisi klien untuk menjaga konsistensi model sisi klien saya.

Setelah beberapa penelitian, saya tidak dapat menemukan posting, artikel, buku atau apa pun yang akan memberikan saran tentang bagaimana seseorang dapat dan harus merancang arsitektur webapp modern dengan beberapa topik tertentu dalam pikiran:

  • Bagaimana cara menyusun data yang dikirim dari server ke pengguna?
    • Haruskah saya mengirim acara seperti "sumber ini telah diperbarui dan Anda harus memuatnya kembali melalui panggilan AJAX" atau mendorong data yang diperbarui dan mengganti data sebelumnya yang dimuat melalui panggilan AJAX awal?
    • Bagaimana cara mendefinisikan kerangka yang koheren dan skalabel untuk data yang dikirim? apakah ini pesan pembaruan model atau pesan "ada kesalahan dengan blahblahblah"
  • Bagaimana tidak mengirim data tentang semuanya dari mana saja di backend?
  • Bagaimana cara mengurangi duplikasi logika bisnis di server dan di sisi klien?
Philippe Durix
sumber
Apakah Anda yakin Rails adalah pilihan terbaik untuk SPA Anda? Rails memang bagus, tetapi ditujukan untuk aplikasi monolith ... Anda mungkin ingin backend modular dengan pemisahan yang terpisah ... Tergantung pada kebutuhan Anda, saya akan mempertimbangkan kerangka kerja Ruby alternatif untuk SPA waktu nyata.
Myst
1
Saya tidak yakin Rails adalah pilihan terbaik, tetapi saya sangat puas dengan tumpukan di tempat setidaknya di backend (mungkin karena saya baik dengan kerangka kerja khusus ini). Perhatian saya di sini adalah lebih lanjut tentang bagaimana merancang SPA pada sudut pandang agnostik teknologi. Dan saya juga tidak ingin melipatgandakan jumlah bahasa, kerangka kerja dan perpustakaan dalam satu proyek.
Philippe Durix
Benchmark yang ditautkan mungkin memiliki masalah, tetapi hal itu mengekspos kelemahan dalam implementasi ActiveRecord saat ini. Saya mungkin bias tentang plezi.io , tetapi seperti yang ditunjukkan dalam hasil benchmark kemudian , ini memberikan peningkatan kinerja yang signifikan, bahkan sebelum pengelompokan dan Redis (yang tidak diuji). Saya pikir kinerjanya lebih baik daripada node.js ... Sampai semuanya berubah, saya akan menggunakan plezi.io.
Myst

Jawaban:

10

Bagaimana cara menyusun data yang dikirim dari server ke pengguna?

Gunakan pola olahpesan . Yah, Anda sudah menggunakan protokol pengiriman pesan, tapi maksud saya struktur perubahan sebagai pesan ... khusus acara. Ketika sisi server berubah, itu menghasilkan peristiwa bisnis. Dalam skenario Anda, pandangan klien Anda tertarik dengan acara ini. Peristiwa harus berisi semua data yang relevan dengan perubahan itu (tidak harus semua data tampilan). Halaman klien kemudian harus memperbarui bagian tampilan yang dipertahankan dengan data acara.

Misalnya, jika Anda memperbarui ticker saham dan AAPL berubah, Anda tidak ingin menekan semua harga saham atau bahkan semua data tentang AAPL (nama, deskripsi, dll). Anda hanya akan mendorong AAPL, delta, dan harga baru. Pada klien, Anda hanya akan memperbarui harga saham itu pada tampilan.

Haruskah saya mengirim acara seperti "sumber ini telah diperbarui dan Anda harus memuatnya kembali melalui panggilan AJAX" atau mendorong data yang diperbarui dan mengganti data sebelumnya yang dimuat melalui panggilan AJAX awal?

Saya akan mengatakan tidak. Jika Anda mengirim acara, silakan dan kirim data yang relevan dengannya (bukan data seluruh objek). Beri nama untuk jenis acara itu. (Penamaan dan data apa yang relevan dengan peristiwa itu berada di luar lingkup kerja mekanis sistem. Ini lebih berkaitan dengan bagaimana logika bisnis dimodelkan.) Tampilan Anda para pemberi pembaruan perlu tahu cara menerjemahkan setiap peristiwa spesifik ke dalam perubahan tampilan yang tepat (yaitu hanya perbarui apa yang berubah).

Bagaimana cara mendefinisikan kerangka yang koheren dan skalabel untuk data yang dikirim? apakah ini pesan pembaruan model atau pesan "ada kesalahan dengan blahblahblah"

Saya akan mengatakan ini adalah pertanyaan besar dan terbuka yang harus dipecah menjadi beberapa pertanyaan lain dan diposting secara terpisah.

Secara umum, sistem back-end Anda harus membuat dan mengirimkan peristiwa untuk kejadian penting pada bisnis Anda. Itu bisa datang dari feed eksternal atau dari aktivitas di back-end itu sendiri.

Bagaimana tidak mengirim data tentang semuanya dari mana saja di backend?

Gunakan pola terbitkan / berlangganan . Ketika SPA Anda memuat halaman baru yang tertarik untuk menerima pembaruan waktu-nyata, halaman tersebut harus berlangganan hanya acara-acara yang dapat digunakan, dan memanggil logika pembaruan tampilan saat peristiwa-peristiwa itu masuk. Anda mungkin perlu pub / sub logika server untuk mengurangi beban jaringan. Perpustakaan ada untuk pub / sub Websocket, tapi saya tidak yakin apa yang ada di ekosistem Rails.

Bagaimana cara mengurangi duplikasi logika bisnis di server dan di sisi klien?

Sepertinya Anda harus memperbarui data tampilan di klien dan server. Dugaan saya adalah Anda memerlukan data tampilan sisi server sehingga Anda memiliki snapshot untuk memulai klien real-time. Karena ada dua bahasa / platform yang terlibat (Ruby dan Javascript), logika pembaruan tampilan harus ditulis dalam keduanya. Selain dari transpiling (yang memiliki masalah sendiri), saya tidak melihat jalan keluarnya.

Poin teknis: Manipulasi data (tampilan pembaruan) bukan logika bisnis. Jika yang Anda maksud validasi use case, maka itu tampaknya tidak dapat dihindari karena validasi klien diperlukan untuk pengalaman pengguna yang baik, tetapi pada akhirnya tidak bisa dipercaya oleh server.


Inilah cara saya melihat hal seperti itu terstruktur dengan baik.

Pandangan Klien:

  • Meminta snapshot tampilan dan nomor acara yang terakhir dilihat
    • Ini akan mempopulasikan tampilan sehingga klien tidak harus membangun dari awal.
    • Bisa lebih dari HTTP GET untuk kesederhanaan
  • Membuat koneksi websocket dan berlangganan ke acara tertentu, mulai dari nomor acara terakhir tampilan.
  • Menerima acara melalui websocket dan memperbarui tampilan berdasarkan tipe acara / data.

Perintah Klien:

  • Minta perubahan data (HTTP PUT / POST / DELETE)
    • Respons hanya berhasil atau gagal + kesalahan
    • (Acara yang dihasilkan oleh perubahan akan muncul di websocket dan memicu pembaruan tampilan.)

Sisi server sebenarnya dapat dipecah menjadi beberapa komponen dengan tanggung jawab terbatas. Satu yang hanya memproses permintaan masuk dan membuat acara. Yang lain bisa mengelola langganan klien, mendengarkan acara (katakanlah dalam proses) dan meneruskan acara yang sesuai kepada pelanggan. Anda dapat memiliki pihak ketiga yang mendengarkan acara dan memperbarui tampilan sisi server - mungkin ini bahkan terjadi sebelum pelanggan menerima acara tersebut.

Apa yang saya jelaskan adalah bentuk CQRS + Messaging , dan strategi khas untuk mengatasi jenis masalah yang Anda hadapi.

Saya tidak memasukkan Sourcing Acara ke dalam deskripsi ini karena saya tidak yakin apakah itu sesuatu yang ingin Anda ambil atau jika Anda membutuhkannya. Tetapi ini adalah pola yang terkait.

Kasey Speakman
sumber
Saya telah banyak mengalami kemajuan dalam topik ini, dan petunjuk yang Anda berikan sangat berguna. Saya menerima jawabannya karena saya menggunakan banyak nasihat, bahkan jika saya tidak menggunakan semuanya. Saya akan menjelaskan jalan yang saya ikuti dalam jawaban lain.
Philippe Durix
4

Setelah beberapa bulan bekerja di backend terutama, saya telah dapat menggunakan beberapa saran di sini untuk mengatasi masalah yang dihadapi platform.

Tujuan utama ketika memikirkan kembali backend adalah untuk menempel sekuat mungkin ke CRUD. Semua tindakan, pesan, dan permintaan yang tersebar di banyak rute dikelompokkan kembali menjadi sumber daya yang dibuat, diperbarui, dibaca atau dihapus . Kedengarannya jelas sekarang, tetapi ini merupakan cara berpikir yang sangat sulit untuk diterapkan dengan hati-hati.

Setelah semuanya diatur ke dalam sumber daya, saya dapat melampirkan pesan waktu nyata ke model.

  • Penciptaan memicu pesan dengan lubang sumber daya baru;
  • Pembaruan memicu pesan dengan hanya atribut yang diperbarui (ditambah UUID);
  • Penghapusan memicu pesan penghapusan.

Pada API Istirahat, semua membuat, memperbarui, menghapus metode menghasilkan respons hanya kepala, kode HTTP yang menginformasikan keberhasilan atau kegagalan dan data aktual yang didorong melalui soket web.

Di ujung depan, setiap sumber daya ditangani oleh komponen tertentu yang memuatnya melalui HTTP saat inisialisasi, kemudian berlangganan pembaruan dan mempertahankan statusnya dari waktu ke waktu. Tampilan kemudian mengikat komponen tesis ini untuk menampilkan sumber daya dan melakukan tindakan pada sumber daya tersebut melalui komponen yang sama.


Saya menemukan CQRS + Messaging dan Event Sourcing membaca sangat menarik, tetapi merasa itu sedikit rumit untuk masalah saya dan mungkin lebih disesuaikan dengan aplikasi intensif di mana melakukan data ke database terpusat adalah kemewahan yang mahal. Tapi saya pasti akan mengingat pendekatan ini.

Dalam hal ini, aplikasi ini akan memiliki beberapa klien secara bersamaan dan saya mengambil partai dengan mengandalkan banyak pada database. Model yang paling berubah disimpan ke Redis yang saya percaya untuk menangani beberapa ratus pembaruan per detik.

Philippe Durix
sumber