Words and Code

One writer’s journey from words to code.

Breaking the News: Wisper + Pub-Sub

#technicaltuesdays, computer science, rails, ruby


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
class PressReview < ActiveRecord::Base
  after_commit :alert_author, on: :create
  after_commit :generate_news_item, on: :create

  private
  def alert_author
      AuthorMailer.send_alert_email(self).deliver_later
  end

  def generate_news_item
      NewsItem.create(self)
  end
end

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
class CreatePressReview
  include Wisper::Publisher
end

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
class CreatePressReview
  include Wisper::Publisher

  def call(press_review_id)
      press_review = PressReview.find(press_review_id)

      # Some logic here to make sure that 
      # the press_review we just found is live and 
      # visible to the public, based on its state

      if press_review.live?
          broadcast(:press_review_created_success, press_review)
      else
          broadcast(:press_review_created_failed, press_review)
      end
  end
end

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
publish(:press_review_created_success, press_review)
announce(:press_review_created_success, press_review)

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
class AuthorAlerter
  def alert_author(press_review_id)
      press_review = PressReview.find(press_review_id)

      AuthorMailer.send_alert_email(press_review).deliver_later
  end
end
1
2
3
4
5
6
class AuthorMailer < ApplicationMailer
  def send_alert_email(press_review)
      # Sends an email to the author
      # alerting them of a new press review
  end
end

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
class NewsItemGenerator
  def generate_news_item(press_review_id)
      press_review = PressReview.find(press_review_id)

      NewsItem.create(press_review_id: press_review_id, published_at: press_review.live_date)
  end
end
1
2
3
4
5
class NewsItem
  belongs_to :press_review

  validates :published_at, presence: true
end

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
class CreatePressReviewController < ApplicationController
  def create
      # The ActiveRecord logic to actually 
      # create a press review would go here

      create_press_review = CreatePressReview.new

      create_press_review.subscribe(AuthorAlerter.new)
      create_press_review.subscribe(NewsItemGenerator.new)

      create_press_review.call(press_review_id)
  end
end

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
class PressReview < ActiveRecord::Base
  include Wisper::Publisher

  belongs_to :author

  after_commit :publish_creation_successful, on: :create

  private
  def publish_creation_successful
      broadcast(:press_review_created_success, self)
  end

  def publish_creation_failed
      broadcast(:press_review_created_failed, self) if errors.any?
  end
end

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.