La modèle de publication-abonnement (ou pub / sub, pour faire court) est un modèle de messagerie Ruby on Rails où les expéditeurs de messages (éditeurs), ne programment pas les messages à envoyer directement à des destinataires spécifiques (abonnés). Au lieu de cela, le programmeur «publie» des messages (événements), sans aucune connaissance de ses abonnés.
De même, les abonnés expriment leur intérêt pour un ou plusieurs événements, et ne reçoivent que les messages qui les intéressent, sans aucune connaissance des éditeurs.
Pour ce faire, un intermédiaire, appelé «courtier de messages» ou «bus d'événements», reçoit les messages publiés, puis les transmet aux abonnés qui sont enregistrés pour les recevoir.
En d’autres termes, pub-sub est un modèle utilisé pour communiquer des messages entre différents composants du système sans que ces derniers ne sachent quoi que ce soit sur l’identité de chacun.
Ce modèle de conception n'est pas nouveau, mais il n'est pas couramment utilisé par Développeurs Rails . Il existe de nombreux outils qui aident à incorporer ce modèle de conception dans votre base de code, tels que:
Tous ces outils ont des implémentations pub-sub sous-jacentes différentes, mais ils offrent tous les mêmes avantages majeurs pour une application Rails.
C'est une pratique courante, mais pas une meilleure pratique, d'avoir des modèles ou des contrôleurs lourds dans votre application Rails.
Le modèle pub / sous peut facilement Aidez-moi décomposer des modèles ou des contrôleurs de graisse .
Avoir beaucoup de rappels entrelacés entre les modèles est un bien connu odeur de code , et petit à petit, il couple étroitement les modèles entre eux, ce qui les rend plus difficiles à maintenir ou à étendre.
Quelle est la meilleure taille pour une équipe Scrum ?
Par exemple a Post
le modèle pourrait ressembler à ce qui suit:
# 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
Et le Post
Le contrôleur peut ressembler à ceci:
# app/controllers/api/v1/posts_controller.rb class Api::V1::PostsController Comme vous pouvez le voir, le Post
model a des rappels qui couplent étroitement le modèle à la fois Feed
modèle et le User::NotifyFollowers
service ou préoccupation. En utilisant n'importe quel modèle pub / sub, le code précédent pourrait être re-factorisé pour être quelque chose comme le suivant, qui utilise Wisper:
# app/models/post.rb class Post # ... field: content, type: String # ... # no callbacks in the models! end
Éditeurs publier l'événement avec l'objet de charge utile d'événement qui pourrait être nécessaire.
# app/controllers/api/v1/posts_controller.rb # corresponds to the publisher in the previous figure class Api::V1::PostsController Les abonnés souscrivez uniquement aux événements auxquels ils souhaitent répondre.
# 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
Bus événementiel enregistre les différents abonnés dans le système.
# config/initializers/wisper.rb Wisper.subscribe(FeedListener.new) Wisper.subscribe(UserListener.new)
Dans cet exemple, le modèle pub-sub a complètement éliminé les rappels dans le Post
modèle et a aidé les modèles à fonctionner indépendamment les uns des autres avec un minimum de connaissances les uns sur les autres, assurant un couplage lâche. Étendre le comportement à des actions supplémentaires est juste une question d'accrochage à l'événement souhaité.
Le principe de responsabilité unique (SRP)
La Principe de responsabilité unique est vraiment utile pour maintenir une base de code propre. Le problème en s'y tenant est que parfois la responsabilité de la classe n'est pas aussi claire qu'elle devrait l'être. Ceci est particulièrement courant en ce qui concerne les MVC (comme Rails).
pourquoi la technologie portable est-elle importante
Des modèles devrait gérer la persistance, les associations et pas grand-chose d'autre.
Contrôleurs doit gérer les demandes des utilisateurs et être un wrapper autour de la logique métier (objets de service).
Objets de service devrait englober l'une des responsabilités de la logique métier, fournir un point d'entrée pour les services externes ou agir comme une alternative aux préoccupations du modèle.
Grâce à sa capacité à réduire le couplage, le modèle de conception pub-sub peut être combiné avec des objets de service à responsabilité unique (SRSO) pour aider à encapsuler la logique métier et empêcher la logique métier de s'insinuer dans les modèles ou les contrôleurs. Cela permet de garder la base de code propre, lisible, maintenable et évolutive.
Voici un exemple d'une logique métier complexe implémentée à l'aide du modèle pub / sub et des objets de service:
Éditeur
# 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 # ...
Les abonnés
# 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
En utilisant le modèle de publication-abonnement, la base de code s'organise presque automatiquement en SRSO. De plus, l'implémentation de code pour des flux de travail complexes est facilement organisée autour d'événements, sans sacrifier la lisibilité, la maintenabilité ou l'évolutivité.
Essai
En décomposant les gros modèles et contrôleurs, et en ayant beaucoup de SRSO, le test de la base de code devient un processus beaucoup, beaucoup plus facile. C'est particulièrement le cas lorsqu'il s'agit de tests d'intégration et de communication inter-modules. Les tests doivent simplement garantir que les événements sont publiés et reçus correctement.
Wisper a un test de gemme qui ajoute des adaptateurs RSpec pour faciliter les tests de différents composants.
Dans les deux exemples précédents (Post
exemple et Order
exemple), les tests doivent inclure les éléments suivants:
Éditeurs
# 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
Les abonnés
# 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
Cependant, il existe certaines limitations pour tester les événements publiés lorsque l'éditeur est le contrôleur.
Si vous voulez aller plus loin, faire tester la charge utile vous aidera également à maintenir une base de code encore meilleure.
c corp s corp llc
Comme vous pouvez le voir, les tests de modèles de conception pub-sub sont simples. Il s’agit simplement de s’assurer que les différents événements sont correctement publiés et reçus.
Performance
C'est plus un possible avantage. Le modèle de conception de publication-abonnement en lui-même n'a pas d'impact inhérent majeur sur les performances du code. Cependant, comme pour tout outil que vous utilisez dans votre code, les outils d'implémentation de pub / sub peuvent avoir un effet important sur les performances. Parfois, cela peut être un mauvais effet, mais parfois cela peut être très bon.
Tout d'abord, un exemple d'un mauvais effet: Redis est un «cache et un stockage avancés de valeurs-clés. Il est souvent appelé serveur de structure de données. » Cet outil populaire prend en charge le modèle pub / sous et est très stable. Cependant, s'il est utilisé sur un serveur distant (et non sur le même serveur sur lequel l'application Rails est déployée), cela entraînera une énorme perte de performances en raison de la surcharge du réseau.
D'autre part, Wisper dispose de divers adaptateurs pour la gestion des événements asynchrones, comme wisper-celluloïd , wisper-sidekiq et wisper-activejob . Ces outils prennent en charge les événements asynchrones et les exécutions threadées. qui, s'il est appliqué correctement, peut considérablement augmenter les performances de l'application.
La ligne de fond
Si vous visez un effort supplémentaire en termes de performances, le modèle pub / sous-marin pourrait vous aider à l'atteindre. Mais même si vous ne trouvez pas d’amélioration des performances avec ce modèle de conception Rails, cela vous aidera à garder le code organisé et à le rendre plus facile à gérer. Après tout, qui peut s'inquiéter des performances d'un code qui ne peut pas être maintenu ou qui ne fonctionne pas en premier lieu?
Inconvénients de l'implémentation Pub-Sub
Comme pour toutes choses, il y a aussi quelques inconvénients possibles au modèle pub-sub.
Couplage lâche (couplage sémantique inflexible)
Les plus grandes forces du modèle pub / sous sont aussi ses plus grandes faiblesses. La structure des données publiées (la charge utile de l'événement) doit être bien définie, et devient rapidement assez rigide. Afin de modifier la structure des données de la charge utile publiée, il est nécessaire de connaître tous les abonnés, et soit de les modifier également, soit de s'assurer que les modifications sont compatibles avec les anciennes versions. Cela rend la refactorisation du code Publisher beaucoup plus difficile.
Si vous voulez éviter cela, vous devez être très prudent lors de la définition de la charge utile des éditeurs. Bien sûr, si vous disposez d'une excellente suite de tests, qui teste la charge utile comme mentionné précédemment, vous n'avez pas à vous soucier beaucoup de la panne du système après avoir modifié la charge utile ou le nom de l'événement de l'éditeur.
Stabilité du bus de messagerie
Les éditeurs n'ont pas connaissance du statut de l'abonné et vice versa. En utilisant de simples outils pub / sub, il peut ne pas être possible d'assurer la stabilité du bus de messagerie lui-même et de s'assurer que tous les messages publiés sont correctement mis en file d'attente et remis.
Le nombre croissant de messages échangés conduit à des instabilités dans le système lors de l'utilisation d'outils simples, et il peut ne pas être possible d'assurer la livraison à tous les abonnés sans certains protocoles plus sophistiqués. En fonction du nombre de messages échangés et des paramètres de performances que vous souhaitez atteindre, vous pouvez envisager d'utiliser des services tels que RabbitMQ , PubNub , Poussoir , CloudAMQP , IronMQ ou de nombreuses autres alternatives. Ces alternatives offrent des fonctionnalités supplémentaires et sont plus stables que Wisper pour les systèmes plus complexes. Cependant, leur mise en œuvre nécessite également un travail supplémentaire. Vous pouvez en savoir plus sur le fonctionnement des courtiers de messages Ici
Boucles d'événements infinies
Lorsque le système est entièrement piloté par des événements, vous devez faire très attention à ne pas avoir de boucles d'événements. Ces boucles sont comme les boucles infinies qui peuvent se produire dans le code. Cependant, ils sont plus difficiles à détecter à l'avance et peuvent entraîner l'arrêt de votre système. Ils peuvent exister sans votre préavis lorsque de nombreux événements sont publiés et souscrits dans le système.
exprimer js vs nœud js
Conclusion du didacticiel Rails
Le modèle de publication-abonnement n'est pas une solution miracle pour tous vos problèmes de Rails et toutes vos odeurs de code, mais c'est un très bon modèle de conception qui aide à découpler les différents composants du système et à le rendre plus maintenable, lisible et évolutif.
Lorsqu'il est combiné avec des objets de service à responsabilité unique (SRSO), pub-sub peut également vraiment aider à encapsuler la logique métier et à empêcher que différents problèmes métier ne s'infiltrent dans les modèles ou les contrôleurs.
Le gain de performances après l'utilisation de ce modèle dépend principalement de l'outil sous-jacent utilisé, mais le gain de performances peut être considérablement amélioré dans certains cas, et dans la plupart des cas, cela ne nuira certainement pas aux performances.
Cependant, l'utilisation du modèle pub-sub doit être étudiée et planifiée avec soin, car avec la grande puissance du couplage lâche vient la grande responsabilité de maintenance et refactorisation composants faiblement couplés.
Étant donné que les événements peuvent facilement devenir incontrôlables, une simple bibliothèque pub / sub peut ne pas garantir la stabilité du courtier de messages.
Et enfin, il y a le danger d'introduire des boucles d'événements infinies qui passent inaperçues jusqu'à ce qu'il soit trop tard.
J'utilise ce modèle depuis près d'un an maintenant, et il m'est difficile d'imaginer écrire du code sans lui. Pour moi, c’est la colle qui permet aux travaux d’arrière-plan, aux objets de service, aux préoccupations, aux contrôleurs et aux modèles de communiquer entre eux proprement et de fonctionner ensemble comme un charme.
J'espère que vous avez appris autant que moi en examinant ce code et que vous vous sentez inspiré pour donner au modèle Publish-Subscribe une chance de rendre votre application Rails géniale.
Enfin, un immense merci à @krisleech pour son excellent travail de mise en œuvre Wisper .