I’ve been rather reflective this past week. This is mostly because the end of this year of technical Tuesdays is now very much in sight, with only a handful more posts left to write. Also, I’ve been going back over old posts and correcting a few spelling and code snippet mistakes that have been brought to my attention (shoutout to all of you who have been proofreading for me!). All of this is to say that I never realized until recently that I’ve covered quite the spread of different topics over the past year!
But here’s the rub: I’m not even close to being done with my list of things I still want to learn more about. And even though that list keeps growing, I’ve noticed that the complexity behind the concepts I’m learning and writing about has begun to slowly change. While I started off focusing on syntax and DSL-specific topics, now those topics have become more theoretical in nature. While I used to write about things like the Rails group_by method and the ampersand operator, now I’m diving into more complex concepts like association callbacks and service objects.
This week took complex concepts to a whole new level. I’m talking about higher-level CS theory that I didn’t even know existed. It all started when I heard someone use the term “pub-sub” (yeah, that’s a thing!). And it stands for publish-subscribe, which is a messaging pattern used in software architecture. If you’ve never heard about this before, don’t worry — I hadn’t either! It’s apparently not all that common in Rails development, but JavaScript promises are a loose example for how they are constructed. But let’s not get carried away with semicolons and such nonesense. How does the publish-subscribe pattern work in Ruby? It’s time to learn all about it!
Extra extra! Read all about pub-sub
In the context of building out systems of software, the publish-subscribe pattern is a way of handling how messages are sent between objects. We are probably already familiar with the concept of the “single responsiblity principle”, or the idea that no method should be responsible for more than one thing. This same concept extends to other parts of our application as well. As we’ve learned through the process of refactoring, our controllers shouldn’t be responsible for the logic that really belongs in model. Similarly, a model shouldn’t be responsible for calling on a third-party service or performing some task or piece of logic that doesn’t really relate to its own state.
The way that we solve this in Ruby is by abstracting out logic into smaller components. We have service objects, which are responsible for carrying out tasks and therefore are easily-testable, and encapuslate a very specific piece of functionality that the rest of the application doesn’t really need to know about.
In Ruby, when we have two objects that are connected in some way — for example, a Dog
belongs_to
its Human
— and the state of the Human
changes, we probably want to notify the instance of the Dog
that the object is associated with. We could say that the Human
sends out a “message” to the objects that are “listening” to it. The real terms that we are trying to use here are publish and subscribe. An instance of a Human
object “publishes” events (i.e., the human wakes_up
, is_ready_to_play
, etc.), and the Dog
object listens and “subscribes” to these events (and probably behaves accordingly, aka it would jump_excitedly
when the human is_ready_to_play
).
Usually, for smaller applications, it’s fine to just rely on one object telling another to behave a certain way explicitly. But, things get kind of messy as you have more objects “listening” to the events of other objects. This is where our knowledge of service objects can come in handy. We can pretty easily abstract out units of work into service classes. But, this still means that we need to notify our service classes whenever they need to change; in other words, we have to tell our services Hey, you need to behave in a certain way because something about the object you’re associated with has changed!
The publish-subscribe pattern uses the exact same concept of sending messages between objects when something about one of the objects changes — however, it does this by using an intermediary object, sometimes called a message broker or an event bus. The important thing here is that the object that does the “publishing” or “broadcasting” of an event has no idea who is listening to its events. It just sends out a signal of sorts. The intermediary message broker object then is responsible for knowing who is “subscribed” to this event, and who needs to know about it. The message broker then makes sure that the correct object gets this message. In the simple example from above, a Human
might publish an event, and another object, such as a DogNotifer
, would be responsible for telling the Dog
instance that it needs to do something.
I really like the way that Ahmed Abdel Razzak explains this in his blog post:
“The publish-subscribe pattern is a Ruby on Rails 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. The pub-sub is a pattern used to communicate messages between different system components without these components knowing anything about each other’s identity.”
This concept can be a little tricky to understand in Ruby until you see all the classes in action. So let’s start publishing and subscribing!
Hush, don’t shout
There are a few different pub-sub gems out there, but the one that I’ve found the easiest to use is a gem called wisper
.
We’ll start the same way that we always do: by adding gem 'wisper'
to our Gemfile
, and then running the bundle
command.
Now, let’s take a look at one of our Ruby classes that we can implement the pub-sub pattern on. Here we have a PressReview
class, that is a representation of a book review that might generate a lot of press for an Author
in our bookstore app. These press reviews are pretty important (think the New York Times Bestseller List, etc.), so we want to notify the author of the book when the press review goes live. We also want to generate a tiny news snippet that will just have a few lines about the article once it has been created:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
|
The first thing that we’ll want to do to add the wisper
gem is to include the Wisper::Publisher
module into the class that is going to be broadcasting events. In this case, we want to broadcast an event when our PressReview
class has been successfully created and has gone “live”. Let’s create a message broker class called CreatePressReview
that will handle the broadcasting of this event. We will either need to include Wisper::Publisher
or alternatively, Wisper.publisher
:
1 2 3 |
|
Next, we’ll need to add the method that is going to be doing the “broadcasting” of the event. It’s pretty typical to use a call
method to do this. Inside of this broadcasting method, we’ll want to handle two different situations (think JavaScript promises): if our press_review
is created succesfully, or if it fails to be created:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
|
We’ll notice that this class takes a press_review_id
, and then contains the logic to set and check whether the press_review
we just found is live
or not. If it is live and we’re ready to notify our author and generate our news item, we’re calling the broadcast
method, and passing it the name of the function we want to execute, along with the press_review
instance. And if the press_review
isn’t live, we’re calling a different method isntead.
It’s worth noting that the broadcast
method is also aliased to publish
and announce
, so either of these lines would have also worked:
1 2 |
|
Before we add any listener objects that will subscribe to these events, let’s first abstract out those alert_author
and generate_news_item
private methods from our PressReview
class into services objects. Our alert_author
method can now be rewritten as a AuthorAlerter
Plain Old Ruby Class, which calls upon an AuthorMailer
:
1 2 3 4 5 6 7 |
|
1 2 3 4 5 6 |
|
And our generate_news_item
method can be refactored into a NewsItemGenerator
service class, that creates a new instance of a NewsItem
:
1 2 3 4 5 6 7 |
|
1 2 3 4 5 |
|
Now that we have our publishers in place, we need to make our service objects actually “listen” to these events.
How And Why To Wisper
Our event listeners will subscribe at runtime to their publishers, which means that they won’t be executed until the broadcast events are actually invoked.
We can make any object a listener that subscribes to broadcast events by calling the subscribe
method. So, inside of our controller, we could do something like this:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
|
We’ll remember that it’s the CreatePressReview
intermediary event bus class that’s actually responsible for broadcasting our events now, not the callbacks in the PressReview
class like we had before! We’re making sure that our AuthorAlerter
and NewsItemGenerator
services are subscribed to the “sucess” and “failure” events of the call
method that is defined in our CreatePressReview
intermediary class. And it’s only when we invoke the call
method (in the last of this controller action) that we’re “broadcasting” our event. We’ve hooked up everything in such a way that the event bus class and the service objects will run the correct code if our press_review
instance actually goes live.
But we’re not limited to doing all of this inside of a controller action! If we wanted to do this directly from our Rails model itself, we could write some similar logic by specificing the methods we want to invoke if the PressReview
was created successfully:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
|
This rewrite has helped us divide our code into smaller, discrete classes that are easily-testable. In fact, we could use the wisper-rspec
gem to help us in stubbing out some tests!
The pub-sub pattern might not be for everyone, but it’s certainly interesting to read and learn about. Even you decide to never use it, at least you can say that you saw a really cute penguin do some serious subscribing at the end of this post:
tl;dr?
- The publish-subscribe pattern is a way to abstract out the conveying of messages between objects. The
wisper
gem is useful for implementing pub-sub in Ruby. - Want to read more about the pub-sub pattern in Rails? Check out this great blog post.
- The
wisper
gem has changed a bit over time, so there are a few good write-ups on how to implement it. Check one out here and here.