Simple pub/sub pattern (OOP) using pure Ruby
Tags: ruby, tutorial, oop
Published: 2020-07-16 07:20:37

---
title: Simple pub/sub pattern (OOP) using pure Ruby
published: true
description: DIY Pub/Sub simple architecture using pure Ruby
tags: ruby, tutorial, oop
cover_image: https://dev-to-uploads.s3.amazonaws.com/i/n46ywtf35uspsqgbzy67.jpg
---
Pub/Sub is a popular pattern for reducing coupling and increasing system modularity. In the Publish-Subscribe model, subscribers await for events they are interested in, and get notified of any event generated by a publisher that matches their registered interest.

To simplify, imagine a radio station (publisher). You (subscriber) tune into the wave of this radio station (subscribe) and start to sing along when you hear a Blink-192 band song (react to event).

## In details

This is a very convenient pattern when you have many parts of the program that must respond differently to an event. For example, imagine a site where user is registered. In this case, an email should be sent, the service of collecting information on the user in social networks should start and many other things. If everything is done in one place, then you will get high coupling and this monster will be hard to maintain. In the pub/sub pattern, you have a service that creates a user and broadcasts a message to everyone who is interested in user creation. All other services isolated from each other will perform the necessary work as soon as they receive the event they need.

![Our simple pubsub solution](https://dev-to-uploads.s3.amazonaws.com/i/oobvkjr7vh1n0f0h0gd0.png)

There are a lot of solutions providing Publish-Subscribe capabilities. From small libraries (One of them a `wisper` gem), to huge projects like RabbitMQ or Kafka. But to understand how it works, we will build our simple personal solution and investigate.

## Our demo

We are building our own Walmart. Let's start with three classes (`Item`, `Checkout` and `Printer`). When cashier scans an item, it should be added to checkout and immediatelly printed. Read the code below carefully. Here is the classical solution. We inject `Printer` as dependency to our `Checkout` and call print on it every time item is added. What problem we have here? `Checkout` needs to now how `Printer` works, that it has `print` method. When you have a lot of classes and interactions, that can be problematic.

```ruby
class Item
  attr_reader :code, :title

  def initialize(code:, title:)
    @code = code
    @title = title
  end

  def to_s
    "#{@code} #{@title}"
  end
end

class Checkout
  attr_reader :items

  def initialize(printer: Printer.new)
    @printer = printer
    @items = []
  end

  def add(item)
    @items << item
    @printer.print(item)
  end
end

class Printer
  def print(message)
    puts "[#{Time.now}] #{message}"
  end
end
```

How we can solve this problem with pub/sub pattern. First, let's introduce `Publisher` module.

```ruby
module Publisher
  def subscribe(subscribers)
    @subscribers ||= [] # if @subscribers is nil, we initialize it as empty array, else we do nothing
    @subscribers += subscribers
  end

  def broadcast(event, *payload)
    @subscribers ||= [] # @subscribers is nil, we can't do each on it
    @subscribers.each do |subscriber|
      # If event is :item_added occured with payload item itself
      # we send method :item_added to subscriber and bypass payload as argument if subscriber
      # responds to it.
      subscriber.public_send(event.to_sym, *payload) if subscriber.respond_to?(event)
    end
  end
end
```

And that's it. Let's refactor our business logic to work with Publisher module

```ruby
class Checkout
  include Publisher
  
  attr_reader :items

  def initialize(subscribers:)
    @items = []
    subscribe(subscribers)
  end

  def add(item)
    @items << item
    broadcast(:item_added, item)
  end
end

class Printer
  def item_added(item)
    print(item)
  end

  private

  def print(message)
    puts "[#{Time.now}] #{message}"
  end
end
```

Let's check it 

```ruby
item1 = Item.new(code: "DRP", title: "Dr.Pepper")
item2 = Item.new(code: "CCL", title: "Coca-Cola")

checkout = Checkout.new(subscribers: [Printer.new])

checkout.add(item1)
checkout.add(item2)

#=> [2020-07-16 12:08:49 +0600] DRP Dr.Pepper
#=> [2020-07-16 12:08:49 +0600] CCL Coca-Cola
```

And now our `Checkout` class doesn't know anything about `Printer` implementation, it just sends event :item_added to it. You can say that it is looking more complex, but you will start to collect benefits with scaling the system. Imaging we add `SoundManager` class, which works with our sound card.

```ruby
class SoundManager
  def item_added(_)
    beep!
  end

  def beep!
    print "\a"
  end
end

item = Item.new(code: "DRP", title: "Dr.Pepper")
checkout = Checkout.new(subscribers: [Printer.new, SoundManager.new])
checkout.add(item)

#=> [2020-07-16 12:08:49 +0600] DRP Dr.Pepper
# And you should hear "Beep" sound!
```

Again, our `Checkout` class doesn't know anything about SondManager implementation and internal logic. You just send event to it and `SoundManager` knows how to handle it!

You can find full final code here: [gist.github.com](https://gist.github.com/pashagray/6efab0b2048badb9166fc068046505d2)

## Pros and Cons of pub/sub

- 👍 Less coupling between classes
- 👍 High level of scalability
- 👍 Compact system tests for each isolated module
- 👍 Easy to implement


- 👎 Full process logic is not visible (You won’t be able to understand what happened with `Checkout#add` method call by simply looking at the class)
- 👎 You still need full integration tests to check system as a whole

## Conclusion

We built the simplest example of such an architecture. There are many more complex solutions, for example, when Publisher needs to make sure that the subscriber received an event. Or when you need to send events asynchronously or after some time. It's a huge topics covering message brokers, data bus, queues, etc. If you are interested in it, I can go further and provide deeper tutorials.