Publish/Subscribe Explained

This article¬†explains how the classic Publish/Subscribe (also known as Observer) Design Pattern works. It is a prerequisite for the next article, “Multiplayer Game with Akka .NET and Publish/Subscribe“.

The source code for this article is available at the Gigi Labs BitBucket Repository.

Publish/Subscribe

A pretty common situation in client/server scenarios is the need to synchronise state across a number of clients. A very simple example of this is a chat web application: one of the clients writes a message, and that message needs to be sent to all the other clients.

Polling is a simple but inefficient way to do this. It means that clients periodically ask the server for updates. This is wasteful because a lot of requests are sent just to check for updates (potentially there may be none at that time), and the server needs to deal with all of them.

A much better way is to use the Observer Design Pattern, less formally known as Publish/Subscribe. In this classical pattern, clients subscribe for updates; then the server spontaneously sends updates when there are any:

pubsub-overview

This means that clients receive updates pretty much in real-time, and traffic is mostly one-way from the server (as it needs to be).

Implementing Publish/Subscribe in any language is easy. Let’s start by looking only at the interfaces our components must implement.

    public interface ISubscriber
    {
        void Notify(string message);
    }

A subscriber needs only to provide a method that the publisher can call when propagating updates.

    public interface IPublisher
    {
        void Subscribe(ISubscriber subscriber);
        void NotifyAll(string message);
        void Unsubscribe(ISubscriber subscriber);
    }

A publisher provides subscribers with the means to subscribe and unsubscribe for updates. It also provides a method used to broadcast updates to all subscribers.

The publisher can do all this simply by maintaining a collection of subscribers:

    public class Publisher : IPublisher
    {
        private List<ISubscriber> subscribers;

        public Publisher()
        {
            this.subscribers = new List<ISubscriber>();
        }

        public void Subscribe(ISubscriber subscriber)
        {
            this.subscribers.Add(subscriber);
        }

        public void NotifyAll(string message)
        {
            foreach (var subscriber in this.subscribers)
                subscriber.Notify(message);
        }

        public void Unsubscribe(ISubscriber subscriber)
        {
            this.subscribers.Remove(subscriber);
        }
    }

Subscription and unsubscription are equivalent to addition and removal from the subscriber collection respectively. In order to broadcast updates, the publisher needs only to iterate over all subscribers and call the method they provide (in this case Notify()).

The subscriber will then receive the message and do something with it. For this simple example, we will just write it to the console. In order to distinguish between subscribers, we will also give them a locally stored GUID:

    public class Subscriber : ISubscriber
    {
        private Guid subscriberGuid;

        public Subscriber()
        {
            this.subscriberGuid = Guid.NewGuid();
        }

        public void Notify(string message)
        {
            Console.WriteLine($"{this.subscriberGuid} received: {message}");
        }
    }

We can now test this using a simple application such as the following:

        static void Main(string[] args)
        {
            var publisher = new Publisher();

            var subscriber1 = new Subscriber();
            var subscriber2 = new Subscriber();
            var subscriber3 = new Subscriber();

            publisher.Subscribe(subscriber1);
            publisher.Subscribe(subscriber2);

            publisher.NotifyAll("Hello!");

            publisher.Subscribe(subscriber3);
            publisher.Unsubscribe(subscriber1);

            publisher.NotifyAll("How are you?");

            Console.ReadLine();
        }

Here is what we get if we run this:

pubsub-example-output

As you can see, NotifyAll() propagates updates to those who are subscribed. Since subscriber3 subscribed late, he missed the “Hello!” update. And since subscriber1 unsubscribed, he missed the “How are you?” update.

Summary

Publish/Subscribe, also known as the Observer pattern, is very powerful. While polling requires continuous checks for updates against the server, Publish/Subscribe reverses this approach to allow direct push notifications from the server. This greatly reduces the number of requests that the server must handle, while at the same time allowing clients to receive updates in real-time.

This pattern is so important that it serves as the foundation for other programming techniques including event-driven programming, data binding, MVVM, real-time web, and more.