Python design patterns #5: observer (aka: pub-sub)

· Architecture, Design Patterns, Python
Authors

The observer is probably the GOF pattern with the most impact on networking. It is a massaging pattern by itself.

It’s quite simple, though. The subscribers, interested with a certain topic, subscribe with a publisher for updates regarding that topic.

The publisher, triggered by our topic-aware system, then publishes the topic updates to the right subscribers.

Use case
Every time we use events and event-handlers we’re actually using a pub-sub mechanism. Same for working with topic-aware message queues.

In a client-server environment there are basically two paradigms for the clients of keeping up to date with the server: polling and server-push. There are numerous pros and cons to discuss about separate, but if server-push is your choice and you have a dynamic number of clients and scenarios then pub-sub is a great way to implement it.

Publisher
The publisher is aware of all available topics and manages each topic’s subscribers.
Each topic is usually identified by a simple id, like a topic name or an event type.
However, it can be easily upgraded to a finer topic-id combination, allowing for a finer subscribing and publishing.

class Publisher(object):

    def __init__(self):
        self.subscriptions = {}

    def subscribe(self, subscriber, topic, key='*'):
        if not self.subscriptions.has_key(topic):
            self.subscriptions[topic] = {}
        if not self.subscriptions[topic].has_key(key):
            self.subscriptions[topic][key] = []

        self.subscriptions[topic][key].append(subscriber)
        print('subscribing {0} to topic \'{1}\' with key \'{2}\''
              .format(subscriber.id_, topic, key))

    def publish(self, data, topic, key='*'):
        if not self.subscriptions.has_key(topic) or not self.subscriptions[topic].has_key(key):
            return

        print('publishing \'{0}\' in topic \'{1}\' with key \'{2}\''
              .format(data, topic, key))
        for subscriber in self.subscriptions[topic][key]:
            subscriber.update(data)
        if key != '*':
            self.publish(data, topic, '*')
 

Subscriber
The subscriber needs some identification and an update call-back method, invoked by the Publisher upon publish.

class Subscriber(object):

    def __init__(self, id_):
        self.id_ = id_

    def update(self, data):
        print('subscriber {0} got \'{1}\''.format(self.id_, data))
 

Cats and dogs
Subscribing people to cats and dogs updates!
Publishing to topics with or without a key.

if __name__ == '__main__':
    pub = Publisher()

    kateSub = Subscriber('kate')
    dougSub = Subscriber('doug')

    pub.subscribe(kateSub, 'cats')
    pub.subscribe(kateSub, 'dogs')
    pub.subscribe(dougSub, 'dogs', 'buster')

    pub.publish('new cats in town!', 'cats')
    pub.publish('a dog named buster was found!', 'dogs', 'buster')
 

Output:
subscribing kate to topic ‘cats’ with key ‘*’
subscribing kate to topic ‘dogs’ with key ‘*’
subscribing doug to topic ‘dogs’ with key ‘buster’

publishing ‘new cats in town!’ in topic ‘cats’ with key ‘*’
subscriber kate got ‘new cats in town!’

publishing ‘a dog named buster was found!’ in topic ‘dogs’ with key ‘buster’
subscriber doug got ‘a dog named buster was found!’

publishing ‘a dog named buster was found!’ in topic ‘dogs’ with key ‘*’
subscriber kate got ‘a dog named buster was found!’

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: