ザ・ パブリッシュ/サブスクライブパターン (または、略してpub / sub)は、メッセージの送信者(発行者)が特定の受信者(サブスクライバー)に直接送信されるようにメッセージをプログラムしないRuby onRailsメッセージングパターンです。代わりに、プログラマーは、サブスクライバーの知識がなくても、メッセージ(イベント)を「公開」します。
同様に、サブスクライバーは1つ以上のイベントに関心を示し、発行者の知識がなくても、関心のあるメッセージのみを受信します。
これを実現するために、「メッセージブローカー」または「イベントバス」と呼ばれる仲介者は、公開されたメッセージを受信し、それらを受信するように登録されているサブスクライバーに転送します。
言い換えると、pub-subは、異なるシステムコンポーネント間でメッセージを通信するために使用されるパターンであり、これらのコンポーネントは互いのIDについて何も知りません。
このデザインパターンは新しいものではありませんが、一般的には使用されていません Rails開発者 。このデザインパターンをコードベースに組み込むのに役立つツールはたくさんあります。
これらのツールはすべて、基盤となるpub-subの実装が異なりますが、Railsアプリケーションに同じ主要な利点を提供します。
Railsアプリケーションにいくつかのファットモデルまたはコントローラーを含めることは一般的な方法ですが、ベストプラクティスではありません。
pub / subパターンは 簡単に 助けて 脂肪モデルまたはコントローラーを分解する 。
たくさん持っている 絡み合ったコールバック モデル間はよく知られています コードの臭い 、そして少しずつモデルを緊密に結合し、保守や拡張を困難にします。
ギリシャ債務危機の根本原因
たとえば、Post
モデルは次のようになります。
# 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
そしてPost
コントローラは次のようになります。
# app/controllers/api/v1/posts_controller.rb class Api::V1::PostsController ご覧のとおり、Post
モデルには、モデルを両方のFeed
に緊密に結合するコールバックがありますモデルとUser::NotifyFollowers
サービスまたは懸念。任意のpub / subパターンを使用することにより、前のコードをリファクタリングして、Wisperを使用する次のようなものにすることができます。
# app/models/post.rb class Post # ... field: content, type: String # ... # no callbacks in the models! end
出版社 必要になる可能性のあるイベントペイロードオブジェクトを使用してイベントを公開します。
# app/controllers/api/v1/posts_controller.rb # corresponds to the publisher in the previous figure class Api::V1::PostsController サブスクライバー 応答したいイベントのみをサブスクライブします。
# 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
イベントバス さまざまなサブスクライバーをシステムに登録します。
# config/initializers/wisper.rb Wisper.subscribe(FeedListener.new) Wisper.subscribe(UserListener.new)
この例では、pub-subパターンにより、Post
のコールバックが完全に排除されました。モデルを作成し、モデルが互いに最小限の知識で互いに独立して動作するのを支援し、疎結合を保証します。動作を追加のアクションに拡張することは、目的のイベントにフックするだけの問題です。
Webアプリケーションの脆弱性トップ10
単一責任原則(SRP)
ザ・ 単一責任の原則 クリーンなコードベースを維持するのに本当に役立ちます。それに固執する際の問題は、クラスの責任が本来あるべきほど明確でない場合があるということです。これは、MVC(Railsなど)に関しては特に一般的です。
モデル 永続性、関連付けなどを処理する必要があります。
コントローラー ユーザー要求を処理し、ビジネスロジック(サービスオブジェクト)のラッパーである必要があります。
サービスオブジェクト ビジネスロジックの責任の1つをカプセル化するか、外部サービスのエントリポイントを提供するか、モデルの懸念事項の代替として機能する必要があります。
結合を減らすその力のおかげで、pub-subデザインパターンを単一責任サービスオブジェクト(SRSO)と組み合わせて、ビジネスロジックをカプセル化し、ビジネスロジックがモデルまたはコントローラーのいずれかに忍び寄ることを防ぐことができます。これにより、コードベースがクリーンで読みやすく、保守可能でスケーラブルに保たれます。
pub / subパターンとサービスオブジェクトを使用して実装されたいくつかの複雑なビジネスロジックの例を次に示します。
出版社
# 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 # ...
サブスクライバー
# 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
パブリッシュ/サブスクライブパターンを使用することにより、コードベースはほぼ自動的にSRSOに編成されます。さらに、複雑なワークフローのコードの実装は、読みやすさ、保守性、またはスケーラビリティを犠牲にすることなく、イベントを中心に簡単に編成できます。
テスト
ファットモデルとコントローラーを分解し、SRSOを多数持つことで、コードベースのテストははるかに簡単なプロセスになります。これは、統合テストとモジュール間通信に関して特に当てはまります。テストでは、イベントが正しく公開および受信されていることを確認するだけです。
ウィスパーには 宝石のテスト これにより、RSpecマッチャーが追加され、さまざまなコンポーネントのテストが容易になります。
前の2つの例(Post
の例とOrder
の例)では、テストに以下を含める必要があります。
出版社
# 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
サブスクライバー
# 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
ただし、発行者がコントローラーである場合、発行されたイベントのテストにはいくつかの制限があります。
さらに一歩進んだ場合は、ペイロードもテストしておくと、さらに優れたコードベースを維持するのに役立ちます。
ご覧のとおり、pub-subデザインパターンのテストは簡単です。さまざまなイベントが正しく公開および受信されるようにするだけです。
パフォーマンス
これはもっと 可能 利点。パブリッシュ/サブスクライブのデザインパターン自体は、コードのパフォーマンスに大きな固有の影響を与えません。ただし、コードで使用する他のツールと同様に、pub / subを実装するためのツールはパフォーマンスに大きな影響を与える可能性があります。悪い影響を与えることもあれば、非常に良いこともあります。
まず、悪い影響の例: Redis は「高度なKey-Valueキャッシュおよびストア」です。多くの場合、データ構造サーバーと呼ばれます。」この人気のあるツールは、pub / subパターンをサポートし、非常に安定しています。ただし、リモートサーバー(Railsアプリケーションがデプロイされているサーバーと同じサーバーではない)で使用すると、ネットワークのオーバーヘッドが原因でパフォーマンスが大幅に低下します。
一方、Wisperには、非同期イベント処理用のさまざまなアダプターがあります。 ウィスパーセルロイド 、 wisper-sidekiq そして wisper-activejob 。これらのツールは、非同期イベントとスレッド化された実行をサポートします。これを適切に適用すると、アプリケーションのパフォーマンスが大幅に向上します。
結論
パフォーマンスをさらに向上させることを目指している場合は、pub / subパターンがそれに到達するのに役立ちます。ただし、このRailsデザインパターンでパフォーマンスの向上が見られない場合でも、コードを整理し、保守しやすくするのに役立ちます。結局のところ、維持できない、またはそもそも機能しないコードのパフォーマンスについて誰が心配できるでしょうか。
Pub-Sub実装のデメリット
すべてのものと同様に、pub-subパターンにもいくつかの欠点があります。
最高財務責任者(cfo)は、会計および財務機能を担当します。
疎結合(柔軟性のないセマンティック結合)
pub / subパターンの最大の長所は、最大の短所でもあります。公開されるデータの構造(イベントペイロード)は明確に定義されている必要があり、すぐに柔軟性がなくなります。公開されたペイロードのデータ構造を変更するには、すべてのサブスクライバーについて知り、それらも変更するか、変更が古いバージョンと互換性があることを確認する必要があります。これにより、パブリッシャーコードのリファクタリングがはるかに困難になります。
これを回避したい場合は、パブリッシャーのペイロードを定義するときに特に注意する必要があります。もちろん、前述のようにペイロードをテストする優れたテストスイートがある場合は、パブリッシャーのペイロードまたはイベント名を変更した後にシステムがダウンすることをあまり心配する必要はありません。
メッセージングバスの安定性
パブリッシャーはサブスクライバーのステータスを認識しておらず、その逆も同様です。単純なpub / subツールを使用すると、メッセージングバス自体の安定性を確保できず、公開されたすべてのメッセージが正しくキューに入れられて配信されることを保証できない場合があります。
交換されるメッセージの数が増えると、単純なツールを使用するとシステムが不安定になり、より高度なプロトコルがないと、すべてのサブスクライバーに確実に配信できない場合があります。交換されるメッセージの数、および達成したいパフォーマンスパラメータに応じて、次のようなサービスの使用を検討できます。 RabbitMQ 、 PubNub 、 プッシャー 、 CloudAMQP 、 IronMQ または他の多くの選択肢。これらの代替手段は追加の機能を提供し、より複雑なシステムではWisperよりも安定しています。ただし、実装するには追加の作業も必要です。メッセージブローカーの仕組みについて詳しく読むことができます ここに
無限のイベントループ
システムがイベントによって完全に駆動される場合は、イベントループが発生しないように特に注意する必要があります。これらのループは、コードで発生する可能性のある無限ループとまったく同じです。ただし、事前に検出するのは難しく、システムを停止させる可能性があります。システム全体で公開およびサブスクライブされているイベントが多数ある場合、通知なしに存在する可能性があります。
Railsチュートリアルの結論
パブリッシュ/サブスクライブパターンは、Railsのすべての問題とコードの臭いに対する特効薬ではありませんが、さまざまなシステムコンポーネントを分離し、保守性、読み取り性、拡張性を高めるのに役立つ非常に優れたデザインパターンです。
単一責任サービスオブジェクト(SRSO)と組み合わせると、pub-subは、ビジネスロジックをカプセル化し、さまざまなビジネス上の懸念がモデルやコントローラーに忍び寄るのを防ぐのにも役立ちます。
このパターンを使用した後のパフォーマンスの向上は、主に使用している基盤となるツールに依存しますが、パフォーマンスの向上は場合によっては大幅に改善でき、ほとんどの場合、パフォーマンスを損なうことはありません。
ただし、疎結合の大きな力には次の大きな責任が伴うため、pub-subパターンの使用は慎重に検討および計画する必要があります。 維持とリファクタリング 疎結合コンポーネント。
イベントは簡単に制御不能になる可能性があるため、単純なpub / subライブラリではメッセージブローカーの安定性が保証されない場合があります。
そして最後に、手遅れになるまで見過ごされてしまう無限のイベントループを導入する危険性があります。
最高財務責任者の役割と責任
私はこのパターンを1年近く使用していますが、それなしでコードを書くことを想像するのは難しいです。私にとって、バックグラウンドジョブ、サービスオブジェクト、懸念事項、コントローラー、モデルがすべて互いにきれいに通信し、魅力のように連携するのは接着剤です。
このコードのレビューから私が学んだことと同じくらい多くのことを学び、パブリッシュ/サブスクライブパターンにRailsアプリケーションを素晴らしいものにする機会を与えることに刺激を受けたことを願っています。
最後に、大いに感謝します @krisleech 彼の素晴らしい仕事の実装に対して ウィスパー 。