--- title: Rails Pub/Sub date: 2014-06-21 draft: true --- The [publish-subscribe pattern][1] (or pub/sub, for short) is a messaging pattern where senders of messages (publishers), do not program the messages to be sent directly to specific receivers (subscribers). Instead, the programmer “publishes” messages (events), without any knowledge of any subscribers there may be. Similarly, subscribers express interest in one or more events, and only receive messages that are of interest, without any knowledge of any publishers. To accomplish this, an intermediary, called a “message broker” or “event bus”, receives published messages, and then forwards them on to those subscribers who are registered to receive them. In other words, pub-sub is a pattern used to communicate messages between different system components without these components knowing anything about each other’s identity. ```ascii ┌──────────┐ ┌──────────┐ ┌─────────────┐ ┌───────────┐ ┌───────────┐ │Publisher1│ │Publisher2│ │MessageBroker│ │Subscriber1│ │Subscriber2│ └────┬─────┘ └────┬─────┘ └──────┬──────┘ └─────┬─────┘ └─────┬─────┘ │ Message A │ │ │ │ ───────────────────────────>│ │ │ │ │ │ │ │ │ │ Message B │ │ │ │ │ ─────────────>│ │ │ │ │ │ │ │ │ │ │ Message A │ │ │ │ │ ─ ─ ─ ─ ─ ─ ─>│ │ │ │ │ │ │ │ │ │ Message B │ │ │ │ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ >│ ┌────┴─────┐ ┌────┴─────┐ ┌──────┴──────┐ ┌─────┴─────┐ ┌─────┴─────┐ │Publisher1│ │Publisher2│ │MessageBroker│ │Subscriber1│ │Subscriber2│ └──────────┘ └──────────┘ └─────────────┘ └───────────┘ └───────────┘ ``` This design pattern is not new, and there are many tools that help incorporate this design pattern into your code base, such as: - [Wisper][2] (which I will focus on in this blog post) - [RabbitMQ][3] - [Redis][4] All of these tools have different underlying pub-sub implementations, but they all offer the same major advantages for a Rails application. # Advantages of Pub-Sub ## Reducing Model/Controller Bloat It is a common practice, but not a best practice, to have some fat models or controllers in your Rails application. The pub/sub pattern can easily help [decompose fat models or controllers][5]. ### Fewer Callbacks Having a lot of [intertwined callbacks][6] between the models is a well-known known code smell, and bit by bit it tightly couples the models together, making them harder to maintain or extend. For example a `Post` model could look like the following: ```ruby # 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 ``` And the `Post` controller might look something like the following: ```ruby # 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 ``` As you can see, the `Post` model has callbacks that tightly couple the model to both the `Feed` model and the `User::NotifyFollowers` service or concern. By using the pub/sub pattern, the previous code could be re-factored to be something like the following: ```ruby # app/models/post.rb class Post # ... field: content, type: String # ... # no callbacks in the models! end ``` **Publishers** publish the event with the event payload object that might be needed. ```ruby # app/controllers/api/v1/posts_controller.rb 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 ``` **Subscribers** only subscribe to the events they wish to respond to. ```ruby # 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** registers the different subscribers in the system. ```ruby # config/initializers/wisper.rb Wisper.subscribe(FeedListener.new) Wisper.subscribe(UserListener.new) ``` In this example, the pub-sub pattern completely eliminated callbacks in the `Post` model and helped the models to work independently from each other with minimum knowledge about each other, ensuring loose coupling. **Expanding the behavior to additional actions is just a matter of hooking to the desired event.** ## The Single Responsibility Principle (SRP) The [Single Responsibility Principle][7] is really helpful for maintaining a clean code base. The problem in sticking to it is that sometimes the responsibility of the class is not as clear as it should be. This is especially common when it comes to MVCs (like Rails). **Models** should handle persistence, associations and not much else. **Controllers** should handle user requests and be a wrapper around the business logic (Service Objects). **[Service Objects][8]** should encapsulate one of the responsibilities of the business logic, provide an entry point for external services, or act as an alternative to model concerns. Thanks to its power to reduce coupling, the pub-sub pattern can be combined with single responsibility service objects (SRSOs) to help encapsulate the business logic, and forbid the business logic from creeping into either the models or the controllers. This keeps the code base clean, readable, maintainable and scalable. Here is an example of some business logic implemented using the pub/sub pattern and service objects: **Publishers** ```ruby # 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 # ... ``` **Subscribers** ```ruby # 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 ``` By using this pattern, the code base gets organized into SRSOs almost automatically. Moreover, implementing code for complex workflows is easily organized around events, without sacrificing readability, maintainability or scalability. ## Testing By decomposing the fat models and controllers, and having a lot of SRSOs, testing of the code base becomes a much, much easier process. This is particularly the case when it comes to integration testing and inter-module communication. Testing should simply ensure that events are published and received correctly. Wisper has a [testing gem][9] that adds RSpec matchers to ease the testing of different components. **Publishers** ```ruby # 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 ``` **Subscribers** ```ruby # 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 ``` However, there are some limitations to testing the published events when the publisher is the controller. If you want to go the extra mile, having the payload tested as well will help maintain an even better code base. As you can see, pub-sub design pattern testing is simple. It’s just the matter of ensuring that the different events are correctly published and received. ## Performance This is more of a *possible advantage*. The publish-subscribe pattern itself does not have a major inherent impact on code performance. However, as with any tool you use in your code, the tools for implementing pub/sub can have a big effect on performance. Sometimes it can be a bad, but sometimes it can be good. # Disadvantages of Pub-Sub Implementation ## Loose Coupling (Inflexible Semantic Coupling) The greatest of the pub/sub pattern’s strengths are also it’s greatest weaknesses. The structure of the data published (the event payload) must be well defined, and quickly becomes rather inflexible. In order to modify data structure of the published payload, it is necessary to know about all the Subscribers, and either modify them also, or ensure the modifications are compatible with older versions. This makes refactoring of Publisher code much more difficult. If you want to avoid this you have to be extra cautious when defining the payload of the publishers. Of course, if you have a great test suite, that tests the payload as well as mentioned previously, you don’t have to worry much about the system going down after you change the publisher’s payload or event name. ## Messaging Bus Stability Publishers have no knowledge of the status of the subscriber and vice versa. Using simple pub/sub tools, it might not be possible to ensure the stability of the messaging bus itself, and to ensure that all the published messages are correctly queued and delivered. The increasing number of messages being exchanged leads to instabilities in the system when using simple tools, and it may not be possible to ensure delivery to all subscribers without some more sophisticated protocols. Depending on how many messages are being exchanged, and the performance parameters you want to achieve, you might consider using services like RabbitMQ, PubNub, Pusher, CloudAMQP, IronMQ or many many other alternatives. These alternatives provide extra functionality, and are more stable than Wisper for more complex systems. However, they also require some extra work to implement. You can read more about how message brokers work [here][9] ## Infinite Event Loops When the system is completely driven by events, you should be extra cautious not to have event loops. These loops are just like the infinite loops that can happen in code. However, they are harder to detect ahead of time, and they can bring your system to a standstill. They can exist without your notice when there are many events published and subscribed across the system. # Conclusion The publish-subscribe pattern is not a silver bullet for all your Rails problems and code smells, but it’s a really good design pattern that helps in decoupling different system components, and making it more maintainable, readable, and scalable. PS: a huge thank you to [@krisleech][10] for his awesome work implementing [Wisper][2]. (used in this article) PPS: This article was originally published on: https://www.toptal.com/ruby-on-rails/the-publish-subscribe-pattern-on-rails [1]: https://en.wikipedia.org/wiki/Publish%E2%80%93subscribe_pattern [2]: https://github.com/krisleech/wisper [3]: https://www.rabbitmq.com/tutorials/tutorial-three-ruby [4]: https://redis.io/docs/interact/pubsub/ [5]: https://codeclimate.com/blog/7-ways-to-decompose-fat-activerecord-models [6]: https://en.wikipedia.org/wiki/Spaghetti_code [7]: https://en.wikipedia.org/wiki/Single_responsibility_principle [8]: https://web.archive.org/web/20140701062528/https://brewhouse.io/blog/2014/04/30/gourmet-service-objects.html [9]: https://en.wikipedia.org/wiki/Message_broker [10]: https://github.com/krisleech