Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C#

Ella publish/subscribe middleware

4.76/5 (7 votes)
24 Sep 2013Ms-PL11 min read 55.1K   442  
Ella is a fully distributed publish/subscribe middleware written in pure C# and compatible with Mono, enabling scalability and flexibility for your application.

Sample Image - maximum width is 600 pixels

Introduction

Building applications which are distributed and/or modular is not quite an easy task. Typically, some kind of framework or middleware is used to provide support for networking, plug-in capabilities and runtime reconfiguration. The idea is, that you as a developer need not to care, where in the network a certain part of your application is runnning as long as it is there. There are quite some middleware systems out there, however you very often see systems which require a central node after all or which are quite difficult to use.
In this article, we want to show you Ella, which is a middleware for (non-centralized) distributed applications.

Ella is a fully distributed publish/subscribe middleware written in pure C# and compatible with Mono. Ella enables scalability and flexibility for your application.

Ella handles all the communication needed to transfer data from a producer to a consumer with no need to care if both are on the same or on different nodes in your network. It discovers other nodes which run Ella instances, so you do not have to care about how to scale your application. The networking functionality can completely be disabled. In this case, Ella is perfect for building modular applications which support plug-ins out of the box. Thus, Ella can be used for both, distributed and non-distributed applications.

Ella is based on the type-based pub/sub flavour because it is very intuitive to handle and does not require topic conventions (in topic-based pub/sub) or a dedicated query mechanism for properties (in case of content-based pub/sub).

Ella was published as open source project and can be found on http://ella.codeplex.com.

Background

Publish/subscribe

Publish/subscribe is an event based middleware paradigm, defining two different roles: (i) the publisher, which produces and publishes data, and (ii) the subscriber, which shows interest in events using subscriptions. Publish/Subscribe, as illustrated in the figure below, is a mechanism which allows for elegant decoupling of functional elements within an application. A module may be publisher and subscriber at the same time, i.e. it processes data from another publisher and publishes the results itself.

A component for publish/subscribe management takes care of decoupling. Instead of directly connecting the publisher and subscriber modules, a publisher announces its events and a subscriber can indicate interest in certain type of events. The publish/subscribe manager takes care of matching published events and subscriptions and is also responsible for delivering the published data to all subscribers. A key requirement of publish/subscribe is that neither publishers nor subscribers need to be aware of each other. A publisher does not need to keep track of where its data is going and how many subscribers exist for its events, and a subscriber does not need to care about where publishers are located and where their data is coming from (i.e. the local node or a remote node). All this is transparently handled by the publish/subscribe middleware. The figure below shows an approach for distributed pub- lish/subscribe. Here, each node runs a local publish/subscribe manager. This manager keeps track of the subscriptions to its local publishers and of the other nodes in the network running the same middleware system.

Sample Image - maximum width is 600 pixels

A publish/subscribe system enables decoupling in the following 3 dimensions:

  • Space decoupling: Modules do not need to know where they and other modules are located in the network. This means that publishers do not hold any references to subscribers and vice versa. In a VSN (Visual Sensor Network), e.g., a publisher of images does not need to care if they are delivered to one or more displays or other modules.
  • Time decoupling: Publishers and subscribers do not need to participate in an interaction at the same time. The publisher might for instance publish an event while there is no subscriber connected. Publishers which start after a subscriber can still be matched to an earlier subscription request. In a VSN, cameras may not start up at the same time, still they must form one distributed application.
  • Synchronization decoupling: Preparing events does not block the publishers, and subscribers can be notified of an event, even though they are currently executing another activity. As an example, a publisher of images can capture the next image while the current image is still delivered to subscribers.

  • The three forms of decoupling enable distributed applications in heterogeneous networks which are scalable, flexible and fault tolerant.

Attributes in Ella

In designing Ella, we did not want to force developers to use any inheritance hierarchy when developing Ella modules. In addition, we wanted a system which makes it very easy to adapt existing code to it. Therefore we decided to use .NET attributes to define Ella-specific code parts.

Attributes are used to indicate certain code areas as relevant to Ella. For example, the attribute [Subscriber] is added to a class to declare it as a subscriber. There are attributes for

  • Declaring classes as publishers and subscribers
  • Factory methods
  • Start and stop methods
  • Message reception methods
  • Event association
  • Template data generator methods
All attributes are defined in the Ella.Attributes namespace.

Using Ella via the facade

If you are developing a module or an application based on Ella, the façade is your place to start.

Accessing Ella-related functions is meant to be as easy and intuitive as possible. To support this, we have decided to use a static facade for it. One important thing when using this middleware is that you do not need to care about holding an instance of Ella in order to access it. Instead, you just call some static methods and Ella takes care of the rest.

There is a collection of public static classes in Ella as shown in the image below. Classes and methods are named in a way that we think is intuitive to use. There are classes related to a certain action and methods related to an object. As an example, to start a publisher you use Start.Publisher.

Sample Image - maximum width is 600 pixels

Start

The start class contains some basic functionality related to starting something.

Start.Ella should be called before accessing any other functions of the middleware.

Start.Network starts up all the networking functionality of Ella. This triggers discovery of other nodes and opening communication ports. To use Ella just on a single node without network, just do not call this method.

Start.Publisher is used to start a publisher module. It adds this publisher to the list of publishers in Ella and makes it possible for subscribers to subscribe to its events. The following example assumes that you have created a publisher class DateTimePublisher which publishes System.DateTime values.

C#
DateTimePublisher publisher = new DateTimePublisher(1000);   //lets the publisher publish a DateTime struct every second

Start.Publisher(publisher);  //this lets Ella know, that this module is ready to be discovered
Ella will now check the publisher for validity and will process its events. Afterwards the starting method of the publisher module will be called in a separate thread. You do not have to start the module yourself. Ella will do that. The publisher just needs to declare a Start method using the Ella.Attributes.StartAttribute.

Note: Subscribers do not need to be started.

Stop

The Stop facade class is analogue to Start. It is used to stop a single publisher or the whole middleware at once. Publish

This facade class is used by publishers to publish their data. Data is published by simply calling Publish.Event(), as shown below.

C#
Publish.Event(DateTime.Now, this, 1);
Ella needs to have the reference to the publisher in order to identify it. 1 is the event number (defined by the publisher itself). This is required since a publisher may publish more than one event. After this call, Ella will find all subscribers (local and remote ones) subscribed to this publisher and will deliver the DateTime object to them. Publish.Event has an additional optional parameter which is a list of SubscriptionHandles. This enables a publisher to publish an event to just a subset of its subscribers.

Writing a subscriber

Subscribers are also pretty easy to implement. They do not interact with Ella until they first perform a subscribe. To define a class as a subscriber, the attribute [Subscriber] is added to the class, as shown below.

C#
[Subscriber]
public class MySubscriber
{
    ...
}
To actually receive data from a publisher, you have to indicate your interest in a certain type. Assume that our subscriber is interested in receiving status messages with a certain custom type.
C#
[Serializable]
public class Status
{
    public string StatusMessage{get; set;}

    public int NodeID{get; set;}

    public DateTime Timestamp {get; set;}
}
Note: Every data object which should be transferred by Ella must be serializable. This is not necessary if you operate locally only.

Then the subscriber will look something like below.
C#
[Subscriber]
public class StatusReceiver
{
    public void Init()
    {
        Subscribe.To<Status>(this, OnNewStatus);
    }

    public void OnNewStatus(Status status)
    {
        //Process the status message
        ...
    }
}
After the Call to Subscribe.To() Ella will immediately check for suitable publishers. This is done locally and on each known Ella node in the network. As soon as any suitable publisher publishes a new Status, it will be delivered to our StatusReceiver.

Optional parameters for subscribing You can add further parameters to the call to Subscribe.To.
  • evaluateTemplateObject: here you pass a delegate to your code which evaluates template objects, i.e., Ella will ask every suitable publisher to provide a template object, and only if your function returns true for one template, your subscriber will be subscribed to it.
  • DataModifyPolicy: Here you can indicate, that you will modify the data which you receive from a publisher. In local operation, you might destroy data which another publisher or subscriber still needs (imagine passing images around and you change pixel values). If necessary, Ella will duplicate each published object before passing it to subscribers in order to prevent accidental data loss. This necessity is determined by comparing the DataModifyPolicy of each subscriber and the DataCopyPolicy of the publisher.
  • forbidRemote: if true Ella will not search for suitable publishers on remote nodes.
  • subscriptionCallback: You can pass a delegate to a method which must be called each time your subscriber is subscribed to a publisher. This will give you a SubscriptionHandle object which you can use to distinguish between events coming from different publishers. So you could distinguish between a StatusObject of a publisher on Node 1 and Node 2.

Writing a publisher

Publishers are simple to implement. Every publisher has to define a [Factory], a [Start] and a [Stop] attribute. For defining a class as a publisher, you just have to add a [Publishes] attribute to your class. This attribute contains additional information including the type that is published, a unique event ID and the copy policy, which indicates whether the published object has to be copied at modification or not. A simple example is shown below, where the class is defined as a publisher of type Status with event ID 1 and no need for copying at modification.

C#
[Publishes(typeof(Status), 1, CopyPolicy = DataCopyPolicy.None)]
public class MyPublisher
{
    ...
}
The [Factory], the [Start] and the [Stop] attribute, required by every publisher, are defined in the same way using attributes.
C#
[Factory]
public MyPublisher
{
    ...
}

[Start]
public void Run()
{
    ...
}

[Stop]
public void Stop()
{
    ...
}
A publisher can now publish data by just using the Publish.Event() method from Ella’s static façade. Now we want build an appropriate publisher for the subscriber above. This means the publisher has to publish the Status.
C#
internal void PublishEvent()
{
       Status status = new Status
	   {
		StatusMessage = "Event 1",
		NodeID = 23,
		Timestamp = DateTime.Now
	   };

        Publish.Event(status, this, 1);
}
For creating a publisher, that is all we have to do. The networking, the discovery of subscribers and the delivery of events to appropriate subscribers is done by Ella.

A sample publisher/subscriber class

A very simple example of a class, which is both, publisher and subscriber is shown below.
C#
[Subscriber]
[Publishes(typeof(Image), 1, CopyPolicy = DataCopyPolicy.None)]
class ChangeDetection
{
    internal Image _image;
    private Image _last;

    [Factory]
    public ChangeDetection()
    {}

    [Start]
    public void Run()
    {
        Ella.Subscribe.To<Image>(this, Callback, DataModifyPolicy.NoModify);
    }

    [Stop]
    public void Stop()
    {}

    internal void PublishEvent()
    {
        Publish.Event(_image, this, 1);
    }

    private void Callback(Image image, SubscriptionHandle handle)
    {
        
        if (_last == null)
            _last = image;
        _image = image.AbsDiff(_last);
        _last = image;

        PublishEvent();

        GC.Collect();
    }
}

The example code above implements a simple change detection. We find the difference between two consecutive images. This is typically used to find motion in video streams.
The class is declared as Publisher and Subscriber using the Attributes. The PublishesAttribute additionally holds information about which type it publishes, which event ID and which copy policy this data has. In this case, objects of type Image are published with the event ID 1 and no need for copying. As already mentioned above, a Publisher has to provide a Factory-, a Start- and a Stop Attribute to create a new instance, start or stop it. Here, the constructor defines the Factory Attribute. In the Run() method, which defines the Start Attribute, the class subscribes itself to the ImageAcquisiton component by simply calling Subscribe.To(). The subscription provides a Callback method, which is called upon arrival of the event data we subscribed to.

At the point in time Subscribe.To() is called, Ella searches for local publishers and if indicated by the subscriber, also for remote publishers that publish objects of matching type. If a suitable publisher is found, the published data is then delivered to the subscriber. The Callback() method is invoked at the point in time, data is arriving from any publisher. So the Callback() method gets the images we subscribed to as a parameter, which means that we now can access or process the images further. That’s now the point we - since we are also a publisher - produce the data to be published. In this case we just perform some calculations to get the motion image we would like to publish. If the data is ready to be delivered to other subscribers, we simply do this by calling Publish.Event().

Summary

In this article, you have read about the Ella publish/subscribe middleware. It is a small piece of software which takes a lot of work from your shoulder when writing modular, distributed applications.
There is much more to say about Ella and how it works. More details can be found on the Ella website, so go and check it out. Please leave comments and let us know what you think and how we can further improve it.

Acknowledgements

Ella has been developed at the Alpen-Adria Universität Klagenfurt, Austria in course of the EPiCS research project .

License

This article, along with any associated source code and files, is licensed under The Microsoft Public License (Ms-PL)