summaryrefslogtreecommitdiffstats
path: root/content/blog/rails-pub-sub/index.md
blob: 8814e6e1de8b83644fe30cc86d8ee3829cba7f3e (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
---
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