Pola Terbitkan-Berlangganan di Rails: Tutorial Implementasi

Diterbitkan: 2022-03-11

Pola publish-subscribe (atau pub/sub, singkatnya) adalah pola pesan Ruby on Rails di mana pengirim pesan (penerbit), tidak memprogram pesan untuk dikirim langsung ke penerima tertentu (pelanggan). Sebaliknya, pemrogram "memublikasikan" pesan (peristiwa), tanpa sepengetahuan pelanggan mana pun yang mungkin ada.

Demikian pula, pelanggan menyatakan minatnya pada satu atau lebih acara, dan hanya menerima pesan yang menarik, tanpa sepengetahuan penerbit mana pun.

Untuk mencapai ini, perantara, yang disebut "broker pesan" atau "bus acara", menerima pesan yang diterbitkan, dan kemudian meneruskannya ke pelanggan yang terdaftar untuk menerimanya.

Dengan kata lain, pub-sub adalah pola yang digunakan untuk mengomunikasikan pesan antara komponen sistem yang berbeda tanpa komponen ini mengetahui apa pun tentang identitas masing-masing.

Dalam tutorial Rails ini, pola desain publish-subscribe ditata dalam diagram ini.

Pola desain ini bukanlah hal baru, tetapi tidak umum digunakan oleh pengembang Rails. Ada banyak alat yang membantu memasukkan pola desain ini ke dalam basis kode Anda, seperti:

  • Wisper (yang saya pribadi lebih suka dan akan membahasnya lebih lanjut)
  • AcaraBus
  • EventBGBus (garpu dari EventBus)
  • KelinciMQ
  • Redis

Semua alat ini memiliki implementasi pub-sub dasar yang berbeda, tetapi semuanya menawarkan keuntungan utama yang sama untuk aplikasi Rails.

Keuntungan Implementasi Pub-Sub

Mengurangi Model/Pengontrol Kembung

Ini adalah praktik umum, tetapi bukan praktik terbaik, untuk memiliki beberapa model atau pengontrol gemuk di aplikasi Rails Anda.

Pola pub/sub dapat dengan mudah membantu menguraikan model atau pengontrol gemuk.

Lebih sedikit Panggilan Balik

Memiliki banyak panggilan balik yang terjalin di antara model adalah bau kode yang terkenal, dan sedikit demi sedikit menyatukan model dengan erat, membuatnya lebih sulit untuk dipertahankan atau diperluas.

Misalnya model Post dapat terlihat seperti berikut:

 # app/models/post.rb class Post # ... field: content, type: String # ... after_create :create_feed, :notify_followers # ... def create_feed Feed.create!(self) end def notify_followers User::NotifyFollowers.call(self) end end

Dan pengontrol Post mungkin terlihat seperti berikut:

 # app/controllers/api/v1/posts_controller.rb class Api::V1::PostsController < Api::V1::ApiController # ... def create @post = current_user.posts.build(post_params) if @post.save render_created(@post) else render_unprocessable_entity(@post.errors) end end # ... end

Seperti yang Anda lihat, model Post memiliki callback yang menghubungkan model dengan erat ke model Feed dan layanan atau perhatian User::NotifyFollowers . Dengan menggunakan pola pub/sub apa pun, kode sebelumnya dapat difaktorkan ulang menjadi seperti berikut ini, yang menggunakan Wisper:

 # app/models/post.rb class Post # ... field: content, type: String # ... # no callbacks in the models! end

Penerbit memublikasikan acara dengan objek payload acara yang mungkin diperlukan.

 # app/controllers/api/v1/posts_controller.rb # corresponds to the publisher in the previous figure class Api::V1::PostsController < Api::V1::ApiController include Wisper::Publisher # ... def create @post = current_user.posts.build(post_params) if @post.save # Publish event about post creation for any interested listeners publish(:post_create, @post) render_created(@post) else # Publish event about post error for any interested listeners publish(:post_errors, @post) render_unprocessable_entity(@post.errors) end end # ... end

Pelanggan hanya berlangganan acara yang ingin mereka tanggapi.

 # app/listener/feed_listener.rb class FeedListener def post_create(post) Feed.create!(post) end end
 # app/listener/user_listener.rb class UserListener def post_create(post) User::NotifyFollowers.call(self) end end

Event Bus mendaftarkan pelanggan yang berbeda dalam sistem.

 # config/initializers/wisper.rb Wisper.subscribe(FeedListener.new) Wisper.subscribe(UserListener.new)

Dalam contoh ini, pola pub-sub sepenuhnya menghilangkan panggilan balik dalam model Post dan membantu model untuk bekerja secara independen satu sama lain dengan pengetahuan minimum tentang satu sama lain, memastikan sambungan longgar. Memperluas perilaku ke tindakan tambahan hanyalah masalah mengaitkan ke acara yang diinginkan.

Prinsip Tanggung Jawab Tunggal (SRP)

Prinsip Tanggung Jawab Tunggal sangat membantu untuk menjaga basis kode yang bersih. Masalah dalam berpegang teguh pada itu adalah bahwa terkadang tanggung jawab kelas tidak sejelas yang seharusnya. Ini sangat umum ketika datang ke MVC (seperti Rails).

Model harus menangani ketekunan, asosiasi, dan tidak banyak lagi.

Pengontrol harus menangani permintaan pengguna dan menjadi pembungkus logika bisnis (Objek Layanan).

Objek Layanan harus merangkum salah satu tanggung jawab logika bisnis, menyediakan titik masuk untuk layanan eksternal, atau bertindak sebagai alternatif untuk masalah model.

Berkat kekuatannya untuk mengurangi kopling, pola desain pub-sub dapat dikombinasikan dengan objek layanan tanggung jawab tunggal (SRSO) untuk membantu merangkum logika bisnis, dan melarang logika bisnis merayap ke model atau pengontrol. Ini membuat basis kode tetap bersih, dapat dibaca, dapat dipelihara, dan dapat diskalakan.

Berikut adalah contoh beberapa logika bisnis kompleks yang diimplementasikan menggunakan pola pub/sub dan objek layanan:

Penerbit

 # app/service/financial/order_review.rb class Financial::OrderReview include Wisper::Publisher # ... def self.call(order) if order.approved? publish(:order_create, order) else publish(:order_decline, order) end end # ...

Pelanggan

 # app/listener/client_listener.rb class ClientListener def order_create(order) # can implement transaction using different service objects Client::Charge.call(order) Inventory::UpdateStock.call(order) end def order_decline(order) Client::NotifyDeclinedOrder(order) end end

Dengan menggunakan pola publish-subscribe, basis kode diatur menjadi SRSO hampir secara otomatis. Selain itu, mengimplementasikan kode untuk alur kerja yang kompleks dengan mudah diatur di sekitar acara, tanpa mengorbankan keterbacaan, pemeliharaan, atau skalabilitas.

Pengujian

Dengan menguraikan model dan pengontrol gemuk, dan memiliki banyak SRSO, pengujian basis kode menjadi proses yang jauh lebih mudah. Hal ini khususnya terjadi dalam hal pengujian integrasi dan komunikasi antar-modul. Pengujian hanya harus memastikan bahwa acara diterbitkan dan diterima dengan benar.

Wisper memiliki permata pengujian yang menambahkan pencocokan RSpec untuk memudahkan pengujian berbagai komponen.

Dalam dua contoh sebelumnya ( Contoh Post dan Contoh Order ), pengujian harus mencakup hal berikut:

penerbit

 # spec/service/financial/order_review.rb describe Financial::OrderReview do it 'publishes :order_create' do @order = Fabricate(:order, approved: true) expect { Financial::OrderReview.call(@order) }.to broadcast(:order_create) end it 'publishes :order_decline' do @order = Fabricate(:order, approved: false) expect { Financial::OrderReview.call(@order) }.to broadcast(:order_decline) end end

Pelanggan

 # spec/listeners/feed_listener_spec.rb describe FeedListener do it 'receives :post_create event on PostController#create' do expect(FeedListner).to receive(:post_create).with(Post.last) post '/post', { content: 'Some post content' }, request_headers end end

Namun, ada beberapa batasan untuk menguji peristiwa yang dipublikasikan saat penerbit adalah pengontrolnya.

Jika Anda ingin bekerja lebih keras, menguji muatan juga akan membantu mempertahankan basis kode yang lebih baik.

Seperti yang Anda lihat, pengujian pola desain pub-sub sederhana. Ini hanya masalah memastikan bahwa berbagai acara dipublikasikan dan diterima dengan benar.

Pertunjukan

Ini lebih merupakan keuntungan yang mungkin . Pola desain publish-subscribe itu sendiri tidak memiliki dampak besar yang melekat pada kinerja kode. Namun, seperti alat apa pun yang Anda gunakan dalam kode Anda, alat untuk mengimplementasikan pub/sub dapat berdampak besar pada kinerja. Terkadang bisa berdampak buruk, tapi terkadang bisa sangat baik.

Pertama, contoh efek buruk: Redis adalah “cache dan penyimpanan nilai kunci lanjutan. Ini sering disebut sebagai server struktur data.” Alat populer ini mendukung pola pub/sub dan sangat stabil. Namun, jika digunakan pada server jarak jauh (bukan server yang sama dengan aplikasi Rails yang digunakan), itu akan mengakibatkan hilangnya kinerja yang sangat besar karena overhead jaringan.

Di sisi lain, Wisper memiliki berbagai adapter untuk penanganan acara asinkron, seperti wisper-celluloid, wisper-sidekiq dan wisper-activejob. Alat-alat ini mendukung peristiwa asinkron dan eksekusi berulir. yang jika diterapkan dengan tepat, dapat sangat meningkatkan kinerja aplikasi.

Garis bawah

Jika Anda menginginkan performa ekstra, pola pub/sub dapat membantu Anda mencapainya. Tetapi bahkan jika Anda tidak menemukan peningkatan kinerja dengan pola desain Rails ini, itu akan tetap membantu menjaga kode tetap teratur dan membuatnya lebih mudah dipelihara. Lagi pula, siapa yang bisa khawatir tentang kinerja kode yang tidak dapat dipertahankan, atau yang tidak berfungsi sejak awal?

Kekurangan Implementasi Pub-Sub

Seperti semua hal, ada beberapa kemungkinan kelemahan pada pola pub-sub juga.

Kopling Longgar (Kopling Semantik Tidak Fleksibel)

Kekuatan terbesar dari pola pub/sub juga merupakan kelemahan terbesarnya. Struktur data yang dipublikasikan (event payload) harus didefinisikan dengan baik, dan dengan cepat menjadi agak tidak fleksibel. Untuk mengubah struktur data dari muatan yang dipublikasikan, Anda perlu mengetahui tentang semua Pelanggan, dan memodifikasinya juga, atau memastikan modifikasi kompatibel dengan versi yang lebih lama. Ini membuat refactoring kode Publisher jauh lebih sulit.

Jika Anda ingin menghindari ini, Anda harus ekstra hati-hati saat menentukan muatan penerbit. Tentu saja, jika Anda memiliki rangkaian pengujian yang hebat, yang menguji payload serta yang disebutkan sebelumnya, Anda tidak perlu terlalu khawatir tentang sistem akan turun setelah Anda mengubah payload penerbit atau nama acara.

Stabilitas Bus Perpesanan

Penerbit tidak memiliki pengetahuan tentang status pelanggan dan sebaliknya. Menggunakan alat pub/sub sederhana, mungkin tidak mungkin untuk memastikan stabilitas bus perpesanan itu sendiri, dan untuk memastikan bahwa semua pesan yang diterbitkan diantrekan dan dikirim dengan benar.

Meningkatnya jumlah pesan yang dipertukarkan menyebabkan ketidakstabilan dalam sistem saat menggunakan alat sederhana, dan mungkin tidak mungkin untuk memastikan pengiriman ke semua pelanggan tanpa beberapa protokol yang lebih canggih. Bergantung pada berapa banyak pesan yang dipertukarkan, dan parameter kinerja yang ingin Anda capai, Anda dapat mempertimbangkan untuk menggunakan layanan seperti RabbitMQ, PubNub, Pusher, CloudAMQP, IronMQ, atau banyak alternatif lainnya. Alternatif ini menyediakan fungsionalitas ekstra, dan lebih stabil daripada Wisper untuk sistem yang lebih kompleks. Namun, mereka juga membutuhkan beberapa pekerjaan ekstra untuk diterapkan. Anda dapat membaca lebih lanjut tentang cara kerja broker pesan di sini

Loop Acara Tak Terbatas

Ketika sistem sepenuhnya didorong oleh peristiwa, Anda harus ekstra hati-hati untuk tidak memiliki loop peristiwa. Loop ini seperti loop tak terbatas yang dapat terjadi dalam kode. Namun, mereka lebih sulit untuk dideteksi sebelumnya, dan mereka dapat menghentikan sistem Anda. Mereka bisa ada tanpa pemberitahuan Anda ketika ada banyak acara yang diterbitkan dan berlangganan di seluruh sistem.

Kesimpulan Tutorial Rails

Pola publish-subscribe bukanlah peluru perak untuk semua masalah Rails Anda dan bau kode, tetapi ini adalah pola desain yang sangat bagus yang membantu dalam memisahkan komponen sistem yang berbeda, dan membuatnya lebih mudah dipelihara, dapat dibaca, dan dapat diskalakan.

Ketika dikombinasikan dengan objek layanan tanggung jawab tunggal (SRSO), pub-sub juga dapat sangat membantu dengan merangkum logika bisnis dan mencegah masalah bisnis yang berbeda dari merayap ke dalam model atau pengontrol.

Peningkatan kinerja setelah menggunakan pola ini sebagian besar bergantung pada alat dasar yang digunakan, tetapi peningkatan kinerja dapat ditingkatkan secara signifikan dalam beberapa kasus, dan dalam banyak kasus tentu tidak akan merusak kinerja.

Namun, penggunaan pola pub-sub harus dipelajari dan direncanakan dengan hati-hati, karena dengan kekuatan besar kopling lepas datang tanggung jawab besar untuk memelihara dan refactoring komponen yang digabungkan secara longgar.

Karena peristiwa dapat dengan mudah berkembang di luar kendali, pustaka pub/sub sederhana mungkin tidak menjamin stabilitas perantara pesan.

Dan akhirnya, ada bahaya memperkenalkan loop peristiwa tak terbatas yang tidak diperhatikan sampai terlambat.


Saya telah menggunakan pola ini selama hampir satu tahun sekarang, dan sulit bagi saya untuk membayangkan menulis kode tanpanya. Bagi saya, ini adalah perekat yang membuat pekerjaan latar belakang, objek layanan, perhatian, pengontrol, dan model semua berkomunikasi satu sama lain dengan bersih dan bekerja sama seperti pesona.

Saya harap Anda telah belajar sebanyak yang saya lakukan dari meninjau kode ini, dan Anda merasa terinspirasi untuk memberikan pola Publish-Subscribe kesempatan untuk membuat aplikasi Rails Anda mengagumkan.

Akhirnya, terima kasih yang sebesar-besarnya kepada @krisleech atas karyanya yang luar biasa dalam mengimplementasikan Wisper.