BT

Modular Java: Declarative Modularity

Posted by Alex Blewitt on Dec 16, 2009 |

In the fourth of the Modular Java series, we'll cover declarative modularity. We'll describe how we can define components and then hook them up together, without having a programmatic dependency on the OSGi APIs.

The previous instalment, Modular Java: Dynamic Modularity, described how to bring dynamic modularity to an application through the use of services. These are implementations that export one (or more) interfaces that can be discovered dynamically at runtime. Whilst this allows for full de-coupling between client and server, it leads to a question of how (and when) the services start up.

Start ordering

In a fully dynamic system, services can not only come and go as a system runs, they can also start in different orders. Sometimes, this isn't a big problem; regardless of the start ordering between A and B, if no events (or threads) actually occur until the system is in a steady state and is ready to accept events, then it shouldn't matter which service gets started first.

However, there's a number of ways that this simple assumption can be violated. The canonical example is of logging; typically, services will connect to and start writing to a log service during start-up and other operations. If a log service is not available, what happens?

Given that services can dynamically come and go at runtime, clients should be able to cope when the service isn't present. In that case, it might be wise to fallback to another mechanism (like printing output to stdout) or blocking to wait for a service to become available (unlikely to be the right answer for logging systems). However, ensuring that there is a service available before starting would be the ideal solution.

Start levels

OSGi provides a mechanism to control the ordering of bundles at start-up, through the use of start levels. These are based on the concept of UNIX run levels; the system starts at level one, and then monotonically increments until it hits the target start level. Each OSGi container provides a different default target start level; for Equinox, the default is 6, whilst for Felix it is 1.

Start levels can therefore be used to create an ordering between bundles, by putting key bundle services (like logging) into a lower start level than those that require it. However, because there are only a finite number of start levels available, and that installers tend to choose single-digit numbers for start levels, it isn't a strong guarantee that you'll fix problems through start order alone.

The other point worth observing is that bundles in the same start level are started independently (and potentially concurrently), so if you have a bundle which has the same start level as a log service, then there's no guarantees that it will wire up when expected. In other words, start levels are good for solving large problems but not necessarily for all problems.

Declarative services

One solution to this problem is OSGi's declarative services, hereafter referred to as DS. In this approach, components are wired together by an external bundle as and when they become available. Declarative services are wired together as defined in an individual XML configuration file, which declares what services are required (consumed) and provided.

In our last example, we used a ServiceTracker to acquire, and if necessary, wait for a service to become available. It would be much more useful if we delay creating the shorten command until the shortening service was available.

DS defines the concept of a component which is at a finer granularity than a bundle, but a larger granularity than a service (since a component may consume/provide multiple services). Each component has a name, corresponds to a Java class, and may be activated or deactivated by calls to that class' methods. Unlike OSGi Java APIs, DS allows for the component to be developed as a pure Java POJO with no programmatic dependencies on OSGi at all. This has the fringe benefit of also making DS easy to test/mock.

In order to demonstrate the approach, we'll be using our example previously. We'll need two components; one of them will be the shortening service itself, and the other will be the ShortenComand that invokes it.

The first task is to configure and register the shorten service with DS. Instead of registering the service via the Bundle-Activator, we can ask DS to register it upon component startup.

So how does DS know to activate or wire this up? Well, we add an entry to the Bundle's Manifest header, which in turn points to one (or more) XML component definition files.

Bundle-ManifestVersion: 2
...
Service-Component: OSGI-INF/shorten-tinyurl.xml [, ...]*

The OSGI-INF/shorten-tinyurl.xml component definition looks like:

<?xml version="1.0" encoding="UTF-8"?> 
<scr:component name="shorten-tinyurl" xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0"> 
	<implementation class="com.infoq.shorten.tinyurl.TinyURL"/>
	<service> 
		<provide interface="com.infoq.shorten.IShorten"/> 
	</service> 
</scr:component>

When DS processes this component, it has roughly the same effect as doing context.registerService( com.infoq.shorten.IShorten.class.getName(), new com.infoq.shorten.tinyurl.TinyURL(), null );. A similar declaration will be needed for the Trim() service, and is included in the source code below.

A single component can provide multiple services under different interfaces if needed. A bundle may also include multiple components, using the same or distinct classes, each of which provides distinct services.

Consuming the service

To consume the service, we need to modify the ShortenCommand so that it binds to an instance of the IShorten service:

package com.infoq.shorten.command;

import java.io.IOException;
import com.infoq.shorten.IShorten;

public class ShortenCommand {
	private IShorten shorten;
	protected String shorten(String url) throws IllegalArgumentException, IOException {
		return shorten.shorten(url);
	}
	public synchronized void setShorten(IShorten shorten) {
		this.shorten = shorten;
	}
	public synchronized void unsetShorten(IShorten shorten) {
		if(this.shorten == shorten)
			this.shorten = null;
	}
}
class EquinoxShortenCommand extends ShortenCommand {...}
class FelixShortenCommand extends ShortenCommand {...}

Note that unlike last time, this has no dependencies on the OSGi APIs; and it would be trivial to mock an implementation to verify that it worked correctly. The synchronized modifier ensures that there's no race conditions when the service gets set.

To tell DS that we need an instance of the IShorten service bound to our EquinoxShortenCommand component, we need to define what services it requires. When DS instantiates your component (with the default constructor), it will wire up the IShorten service by invoking the method defined in the bind attribute; in other words, setShorten().

<?xml version="1.0" encoding="UTF-8"?> 
<scr:component name="shorten-command-equinox" xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0"> 
	<implementation class="com.infoq.shorten.command.EquinoxShortenCommand"/>
	<reference
		interface="com.infoq.shorten.IShorten"
		bind="setShorten" 
		unbind="unsetShorten" 
		policy="dynamic"
cardinality="1..1"
/> <service>
<provide interface="org.eclipse.osgi.framework.console.CommandProvider"/>
</service>
</scr:component>

As soon as the IShorten service is available, this component will be instantiated and wired to the service, regardless of the start ordering between this and the other bundles. The explaination of the policy, cardinality and service provide are covered in the next section.

Policies and cardinality

The policy can be either static or dynamic. A static policy will mean that once set, a service doesn't get changed. If the service goes away, the component is deactivated; and if a new service arrives, then a new instance is created and the service re-bound. This is obviously heavier weight than if we can update the service in place.

With the dynamic policy, when the IShorten service is changed, DS will invoke the setShorten() with the new service, and subsequently unsetShorten() with the old one.

The reason that DS calls the set before the unset is to maintain continuity of the service. If a call came in as the service was being replaced, and the unset was called first, there would be a chance that the shorten service could be null transiently. It's also why the unset method takes an argument, rather than just setting the service to null.

The cardinality of the service, which defaults to 1..1, is one of:

  • 0..1 Optional, maximum of one
  • 1..1 Mandatory, maximum of one
  • 0..n Optional, many
  • 1..n Mandatory, many

If the cardinality can't be satisfied (for example, it is mandatory there is no shortening service), then the component is deactivated. If many services are required, then the setShorten() will be called once for each service. Conversely, the unsetShorten() will be called for each service that goes away.

Not shown here is the ability for the component to do per-instance customisation when it's brought on-line.

In DS 1.1, the component element can also have an activate and deactivate attribute, corresponding to the method which is invoked upon component activation (starting) and deactivation (stopping).

Lastly, this component also provides an instance of the CommandProvider service. This is an Equinox-specific service which allows console commands to be provided, and was formerly done in the bundle's Activator. The advantages of this model are that as soon as the dependent services are available, the CommandProvider service will automatically be published; in addition, the code itself doesn't need to depend on any OSGi APIs.

A similar solution needs to be implemented for the Felix-specific implementation; since as yet, there's no standard for OSGi command shells. There is OSGi RFC 147, which is a work in progress to permit commands being used in different consoles. The source code included has the shorten-command-felix component definition for completness.

Starting the services

The above allows us to start the bundles for providing (and consuming) the shortening service in any order. Once the command service is started, it will bind to the highest priority shortening service available; or, if that's not specified, the one with the lowest service ranking. Should a higher priority service be started afterwards, we currently don't take into account and continue to use the service we are currently bound to. However, should a service go away, then we'll be re-bound to the remaining highest priority shortening service at that time, without interruption from the client.

In order to run the examples, you'll need to download and install some extra bundles for each platform:

By now, you should be familiar with installing and starting bundles; but if not, refer to the Static Modularity article. We'll need to install the above bundles, as well as our shortening service. This is how it would look in Equinox, with the bundles in /tmp

$ java -jar org.eclipse.osgi_* -console
osgi> install file:///tmp/org.eclipse.osgi.services_3.2.0.v20090520-1800.jar
Bundle id is 1
osgi> install file:///tmp/org.eclipse.equinox.util_1.0.100.v20090520-1800.jar
Bundle id is 2
osgi> install file:///tmp/org.eclipse.equinox.ds_1.1.1.R35x_v20090806.jar
Bundle id is 3
osgi> install file:///tmp/com.infoq.shorten-1.0.0.jar
Bundle id is 4
osgi> install file:///tmp/com.infoq.shorten.command-1.1.0.jar
Bundle id is 5
osgi> install file:///tmp/com.infoq.shorten.tinyurl-1.1.0.jar
Bundle id is 6
osgi> install file:///tmp/com.infoq.shorten.trim-1.1.0.jar
Bundle id is 7
osgi> start 1 2 3 4 5
osgi> shorten http://www.infoq.com
...
osgi> start 6 7
osgi> shorten http://www.infoq.com
http://tinyurl.com/yr2jrn
osgi> stop 6
osgi> shorten http://www.infoq.com
http://tr.im/HCRx
osgi> stop 7
osgi> shorten http://www.infoq.com
...

Once we've installed and started our dependencies, including the shorten command, it still doesn't show up in the console. It's only when we start the shortening services that the shorten command is registered.

When the first shortening service is stopped, the implementation automatically fails back over to the second shortening service. When that's stopped, the shorten command service gets automatically unregistered.

Notes

Declarative Services makes wiring OSGi services easy. However, there are a few points to be aware of.

  • The DS bundle needs to be installed and started in order to wire up components. As such, it's typically installed as part of the OSGi framework start-up, such as Equinox's osgi.bundles or Felix's felix.auto.start property.
  • The DS often has other dependencies which need to be installed. In Equinox's case, it includes the equinox.util bundle.
  • Declarative Services is part of the OSGi Compendium Specification rather than the Core Specification, so it's often the case that a separate bundle needs to be made available for the service interfaces. In Equinox's case, it's provided by osgi.services, but in Felix, the interface is exported by the SCR (Service Component Registry, aka DS) bundle itself.
  • Declarative Services can be configured with properties. It typically makes use of the OSGi Config Admin service; although this is optionally bound/accessed. Therefore, some parts of the DS requires Config Admin to be running; and in fact, Equinox 3.5 has a bug which requires Config Admin to be started before Declarative Services if it is used. This often requires the use of the start-up properties above in order to ensure the correct dependencies are met.
  • The OSGI-INF directory (along with XML files) needs to be included in the bundle, as otherwise DS won't be able to see it. You also need to ensure that the Service-Component header is present in the bundle's manifest.
  • It's also possible to use Service-Component: OSGI-INF/*.xml to include all components rather than listing them individually by name. This also permits fragments to add new components to a bundle.
  • The bind and unbind methods need to be synchronized in order to avoid potential race conditions, although using compareAndSet() on an AtomicReference can also be used to act as a non-synchronized placeholder for the single service
  • DS components needs no OSGi interfaces, and as such, can be mocked for testing or used in other inversion of control patterns like Spring. However, there are both Spring DM and the OSGi Blueprint service which can be used to wire services up, which is the subject for a future topic.
  • DS 1.0 didn't define a default XML namespace; DS 1.1 adds the namespace http://www.osgi.org/xmlns/scr/v1.1.0. If no namespace is present, it assumes DS 1.0 compatibility.

Summary

In this article, we've explored how we can decouple our implementations from the OSGi API, and instead use declarative representations of those components. Declarative Services provides both wiring between components and registering of services, which helps to avoid any start-up ordering dependencies. Furthermore, the dynamic nature means that as our dependent services come and go, so too does our component/service come and go as well.

Finally, whether using DS or manually managed services, they all use the same OSGi service layer in order to communicate. Therefore, one bundle could provide services via a manual method, and another could consume it using declarative services (or vice versa). We should be able to mix and match the 1.0.0 and 1.1.0 implementations and they should transparently work.

Installable bundles (also containing source code):

Hello stranger!

You need to Register an InfoQ account or or login to post comments. But there's so much more behind being registered.

Get the most out of the InfoQ experience.

Tell us what you think

Allowed html: a,b,br,blockquote,i,li,pre,u,ul,p

Email me replies to any of my messages in this thread

Minor corrections by Neil Bartlett

1. The shorten() method of your component accesses the shorten field outside of a synchronized block. This is not thread-safe.

2. The activate/deactivate attributes are on the top-level component element, not on the reference element. Also the default method names of "activate" and "deactivate" are assumed, so you only really need to use the attributes if your lifecycle methods are named "begin/end", "commence/cease" etc.

Start Levels do not bound or order DS service activation by Michael Furtak

I believe it is further worth stating that the start level service cannot guarantee that all of the services from bundles in a given level are activated before bundles from the next start level are processed. I know Equinox processes DS services asynchronously on a separate thread, and such may even be a requirement of the spec.

As the author mentions, you can sometimes see positive results by fiddling with the start levels, but in essence you are depending on a race condition. As was stated, it is a much better plan to make sure that your services have no start-order dependencies.

In short, my advice is don't depend on start levels. :)

Re: Minor corrections by Christopher Brind

What are the race conditions mentioned in this article? Are there multiple threads creating DS components?

And wrt to Neil's point 1: in this particular example we're talking about a command executed from the command line - surely only one thread will be accessing it at a time, i.e. the console thread?

Or is it that synchronization is a general problem for DS components?

If you create a component that is also a service which happens to have multiple consumers, then start synchronizing the interface methods and code blocks, you instantly create a system bottleneck, since your consumers will have to take it in turns to access the synchronized blocks.

I just had a scan through the spec and it doesn't mention the need for using synchronization at all in DS components.

What am I missing? :)

Cheers,
Chris

Re: Minor corrections by Alex Blewitt

I don't believe you necessarily need to access the 'shorten' field in a synchronized block. There is precisely one field lookup; that will return whatever the value of the field is at the time; it doesn't matter if there is another thread setting/unsetting at the time. Of course, it might be null; but there's still the condition that a concurrent unset at the same time of a shortern might lead it to be null in usual case. What you don't want to do is access it more than once, which would lead to inconsistencies. Something like:

IShorten s = this.shorten;
if(s!=null) s.shorten(url)


should suffice. I don't think you need to guard the access to the 'shorten' field by synchronised access at all though; since field lookup will be atomic (and if you're not synchronizing the whole method, then clearly it can change between the access and the invocation.

Thanks for the other correction; will fix the article.

Re: Minor corrections by Alex Blewitt

What are the race conditions mentioned in this article? Are there multiple threads creating DS components?


There could be.

And wrt to Neil's point 1: in this particular example we're talking about a command executed from the command line - surely only one thread will be accessing it at a time, i.e. the console thread? Or is it that synchronization is a general problem for DS components?


It's more of a general case best practice, rather than this specific example. However, whilst there's (probably) only one thread executing the console, there could be another process that is shutting down my shorten service asynchronously; for example, a failover process that reaps services running every 24h might decide to stop a bundle at the same time I'm using it. So it's something to be aware of, even if it's not directly relevant in this example.


If you create a component that is also a service which happens to have multiple consumers, then start synchronizing the interface methods and code blocks, you instantly create a system bottleneck, since your consumers will have to take it in turns to access the synchronized blocks.


Right, there are potential bottleneck processes in this kind of situation. Another possibility is to store the var in an AtomicRef, which serializes access to the contained variable. The important thing is to ensure that any long-running service processing doesn't hold the lock, only the acquisition of the service.

Alex

Re: Minor corrections by Neil Bartlett

Alex, I believe you're wrong; all accesses to shared variables need to be made from a synchronized block, otherwise some really mind-bending error conditions can occur. These are well documented in Brian Goetz's book.

Unfortunately that means you will be inside a synchronized block when you call the IShorten service, which is also not recommended; it's better to use the synchronized block only to copy the field into a local variable. Far better to use an AtomicReference (or if the reference is multiple, i.e. 0..n or 1..n, then use a CopyOnWriteArray).

However in this example I would simply use the static policy. The component class is not expensive to (re)create and the reference is mandatory anyway. Static policy means you don't have to worry about concurrency at all.

Re: Minor corrections by Neil Bartlett

Chris, you're right, the DS spec does not mention that you need to synchronize. Neither does it say you *don't* need to synchronize, or that you don't need to write thread-safe code :-)

In OSGi, all bundles are free to create threads -- this is a big difference from, say, J2EE. Therefore service method invocations can occur on any arbitrary thread. Also bundles can call OSGi APIs such as registerService() from any thread, and the events arising from another bundle registering a service are delivered synchronously on that same thread. Therefore the bind/unbind methods in a DS component (when using dynamic policy) can also be called from any arbitrary thread. All this means that, e.g. you might get the unbind called on one of your service references *while you are using it*.

The static policy makes this easier, it simply recreates the component each time the reference change, so you don't have to worry about visibility or locking. But obviously that carries a price tag which might be quite high depending on the resources used by your component, so it's a trade off that you have to think about.

Re: Minor corrections by San Dokan

Alex, the problem Neil is referring to is not related to atomicity, but visibility of value. You're simply not guaranteed that other threads will see latest value of your shorten variable unless you use some kind of synchronization when both writing or reading the field.

Thanks by Lars Vogel

Thanks Alex for this article. Very informative. I'm looking forward for the blueprint services part of your series (in case you have such a part planned).

Re: Minor corrections by Alex Blewitt

Whilst I don't disagree that the visibility of value is something that won't see the latest value of the shorten variable, I disagree that it makes a difference in this case.

Consider the following. We have two service instances, s1 and s2. Initially, the variable is pointing to s1. When it gets stopped/started, the variable is pointing to s2.

The race condition thus becomes a possibility of (another) thread holding a reference to s1 whilst it is being stopped/started. Consider the call paths:

1. Synchronize Block to acquire
2. Copy into local variable
3. Release block
4. Invoke method on local variable

Now consider the non-synchronized case:

1. Copy value of field into stack
2. Invoke method on stack

Both of these admit the same problem; namely, after point 3 (in the first case) and 1 (in the second) can have the service swapped out. So in both cases, you can still invoke on the 'old' service. In other words, the race condition holds unless you serialize access to all shorten services.

Also, bear in mind that this race condition exists in pretty much all Java code everywhere that uses Threads - it's nothing specific to OSGi (or services, for that matter).

Finally, consider the case where the service is a long-running service (say, one minute). If the service is running whilst the bundle is stopped/started, you're still going to be in the 'old' code path, pinned to the old bundle until it completes. Arguing that a subsequent thread that might come in might not see the same initial starting point is just quibbling on semantics.

Excellent articles by Luís Carlos Moreira da Costa

Luis Carlos Moreira da Costa

Allowed html: a,b,br,blockquote,i,li,pre,u,ul,p

Email me replies to any of my messages in this thread

Allowed html: a,b,br,blockquote,i,li,pre,u,ul,p

Email me replies to any of my messages in this thread

11 Discuss

Educational Content

General Feedback
Bugs
Advertising
Editorial
InfoQ.com and all content copyright © 2006-2014 C4Media Inc. InfoQ.com hosted at Contegix, the best ISP we've ever worked with.
Privacy policy
BT