About
I have recently given a presentation on some of my work on the Coalevo project and I think it's good material for an installment on OSGi design practice.
One big issue for good component and service oriented architectures is how to achieve loose coupling between components by design. As architects we are looking for minimization of dependencies and maximization of flexibility, preparing a system for future change. The OSGi framework is a great foundation for this endeavor, nonetheless, loose coupling between your components or services needs to be designed, it doesn't just come from building on top of OSGi.
So the question that needs answering is:
How can I design on top of OSGi and obtain a solution with loose coupling?
I think that a very good answer to this question is what I would like to call the OSGi mediator pattern. The rest that follows now is a description of this pattern, and should also make clear why I like to call it OSGi mediator pattern.
The problem
Now, let's first take a look at a problem: we will try to design a simple text transformation system that is modular and extensible, i.e. one can add and leverage future transformations at runtime. The main task, transforming from a text input to a text output, is visualized in the following Figure:

As a simple example let's say you want to translate BBCode to HTML:
[b]bold text[/b] to <b>bold text<\b>
Now, think of a service that wants to use the transformation from BBCode to HTML. Thinking in OSGi framework you may consider making a bundle with the BBCode transformer, export the required classes and register the transformer as service and then import classes and lookup the transformer from the "consumer" service that wants to realize the transformation.
Sounds good, but now, let's say you suddenly need to transform some implicit markup support as commonly used on Wikis to BBCode and HTML. e.g. let's say for example:
*bold text* to [b]bold text[/b]
So you set out and write another bundle with the Wiki Markup transformer, export the required classes and register the transformer as service and then import classes and lookup the transformer from the "consumer" service that wants to realize the transformation. When you reach the third transformation, you will definitely realize that this isn't a good solution, because the elements in this solution are too tightly coupled. Too much runtime and compile time dependencies have to be taken into account to really make it a feasible solution for future change.
Instead we better architect this with a few simple steps into a better and feasible solution using the OSGi Mediator pattern.
Hands on - the OSGi mediator pattern
So what is this pattern, and why I'd call it this way? Let's see.
First let's consider the Mediator behavioral OO design pattern in [1].
Author's Note: I am convinced that [1] shouldn't be missing on the bookshelf of any OO software architect or engineer :) It is the book on OO design patterns.
[1] describes the Mediator as follows:
Define an object that encapsulates how a set of objects interact. Mediator promotes loose coupling by keeping objects from referring to each other explicitly, and lets you vary their interaction independently.
Now, designing on top of the OSGi platform, we are mainly interested in achieving loose coupling between bundles. Within the formerly presented problem, we are interested in introducing new functionality (i.e. text transformations) with new bundles, without introducing additional new dependencies for bundles that want to use different transformations.
So, what we need is a Mediator bundle, which, given that we are additionally interested in a service oriented architecture, provides a mediating service. Following Figure shows a simple UML class diagram of the service interface defined for our example:

Now, by the definition given in [1], a mediator
...controls and coordinates a group of objects.
So the question which objects are coordinated needs an answer. For our example this answer is relatively simple: the transformers that provide the actual transformation capability. These can be modeled as shown in the class diagram in the following Figure:

Author's Note: The last two methods in the Transformer interface here are just a small idea I had in mind when doing the model, I'll come back later to that idea, and maybe it becomes clear what they are good for. For the moment you can safely ignore them.
Amazingly enough, that's what we need in terms of the public API, except maybe for some Exceptions that will indicate problems with transformation or non existing transformation paths. All dependency at compile time will be covered by the service and the model interfaces and some few exceptions. At run time other services or components that need a transformation may request it from the implementation of the TransformationService at any time.
However, we still have to look into the mechanics that take care about the runtime dependencies. This part of the OSGi mediator pattern can be realized using a design methodology that is often called the OSGi whiteboard model. It pretty much resembles IoC, or what Peter Kriens [2] calls
"Don't call us, we'll call you!"
and the service tutorial at Knopflerfish [3]
"Don't call getService(), call registerService() instead!" .
The design of the white board for the mediator pattern is relatively simple: we to listen to the OSGi service registrations for Transformer instances (in this sense implementations of Transformer are an OSGi service as well) and manage them for the TransformationService. In the example design I'll use a dedicated class, but that is not relevant for the pattern itself:

The following sequence diagram illustrates what happens when a new Transformer implementation (in this case from a BBCode providing bundle) becomes available in the container:

Subsequently any other service or component may use the TransformationService for BBCode transformation:

When adding new bundles that register Transformer instances, they will auto-magically become available to the TransformationService, and thus, without new imports or changing other bundles, directly to any other service that
wants to use the transformations.
Wrapping it up
The implementation based on the design pattern I described, does exactly what it is supposed to do: it provides an architectural design that promotes loose coupling between components, although they are used together to provide some kind of functionality in your system. Maybe this becomes more obvious with a small deployment diagram of the example:

[1] Gamma, E., Helm, R., Johnson, R., Vlissides, J., January 1995. Design Patterns: Elements of Reusable Object-Oriented Software. Professional Computing Series. Addison-Wesley.
[2] http://www.osgi.org/blog/2006/05/why-installers-are-evil.html
[3] http://www.knopflerfish.org/osgi_service_tutorial.html#white
PS: The note I made about the Transformer interface is basically about the fact that the mediator implementation may provide "composed" Transformer instances. e.g. TML->HTML may not be available directly, but maybe using TML->BBCode and then BBCode -> HTML (The example isn't as important as the idea itself.) Now, the mediator may actually manage all available Transformer instances in a simple network, and then resolve a shortest path problem between a specific source (e.g. TML) and a specific sink (e.g. HTML). This is what the two extra methods are actually good for, they may provide a cost factor of a transformation using a specific Transformer implementation (i.e. getNumberOfPasses() and a way to know if a Transformer is composed, not provided directly by some bundle/implementation.


0 comments:
Post a Comment