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.
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.
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.
DateTimePublisher publisher = new DateTimePublisher(1000);
Start.Publisher(publisher);
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.
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.
[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.
[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.
[Subscriber]
public class StatusReceiver
{
public void Init()
{
Subscribe.To<Status>(this, OnNewStatus);
}
public void OnNewStatus(Status status)
{
...
}
}
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.
[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.
[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.
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.
[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
.