Wednesday, January 27, 2010

ServiceLoader and how it relates to OSGi

An important aspect of good software architecture is that clients of your components can interact with well defined and stable APIs. The concept of interfaces is an inherent part of the Java programming language and also central to the OSGi Service Layer.

Lets have a look at a simple service that I want to migrate from a plain Java programming model to OSGi.

public interface AccountService
{   
   /** Get the current balance */   
   long getBalance();   

   /** Credit the given amount and return the current balance */   
   long credit(long amount);   

   /** Withdraw the given amount and return the current balance */   
   long withdraw(long amount);
}

This interface would be part of the API jar (i.e. serviceloader-api.jar) that I package and distribute to the clients of my AccountService implementation. I have another jar that contains the implementation (i.e. serviceloader-impl.jar) and also a file in META-INF/services that names the implementation class



The implementation of my AccountService would be in a different package than the interface, for example something like '...serviceloader.service.internal'. Clients would be encouraged never to import anything from '*.internal'

A Java client could obtain an instance of the AccountService by using the well known ServiceLoader that is part of JDK1.6.

ServiceLoader loader = ServiceLoader.load(AccountService.class);
AccountService service = loader.iterator().next();

So far so good, but there are a number of issues with this approach. The most obvious one is that of the potential availability of more than one implementation of the same interface. The ServiceLoader uses the current thread's context class loader to discover all available service implementations. It then uses the same class loader to load the service instances.

The client gets presented with an iterator of available service implemenations, but what are the criteria does the client use to choose the specific implementation it really needs? We could add something like getVersion(), getVendor(), getSomeOtherProp() to our AccountService, which the client could then use to pick the implementation it needs, but this approach would pollute our AccountService with concerns that are not about accounting.

Another potential problem is that the client could directly instantiate or cast the service instance to gain access to internal functionality, which bypasses the fundamentals of abstraction and modularity that motivated us to use a service interface in the first place.

How does OSGi solve this problem?

In OSGi, a bundles can define the transitive closure of the API that it wants to make publicly available. In case of our API bundle the OSGi manifest could look like this

Bundle-ManifestVersion: 2
Bundle-SymbolicName: example-serviceloader-api
Export-Package: example.serviceloader.service;version="1.0"

Please note, that we associate a specific version with the package that contains the service interfaces. This is the version of our public contract. The package is versioned, not the API bundle. This allows us to rethink and change our packaging strategy in the future.

The bundle that contains the implementation could have a manifest like this

Bundle-ManifestVersion: 2
Bundle-SymbolicName: example-serviceloader-impl
Bundle-Vendor: JBoss, a division of RedHat
Bundle-Version: 1.0.0
Import-Package: example.serviceloader.service;version="[1.0,1.1)"
Private-Package: example.serviceloader.service.internal

The implementation explicitly states that it is valid for API version greater and equal to 1.0 up until but not including 1.1. Please also note, that the '*.internal' package is declared private, which prevents any other bundle from seeing the contents of that package even though the individual classes may be public. A client running in an OSGi environment cannot instantiate the implementation directly, nor can it cast to it.

The implementation bundle could register the AccountService with the framework in BundleActivator.start() like this

Hashtable props = new Hashtable();
props.put("service.vendor", "JBoss, a division of RedHat");
AccountService service = new AccountServiceImpl();
context.registerService(AccountService.class.getName(), service, props);

Please note, that we associate some meta data with the service registration. In this case the service vendor, which could be used by a client to obtain a specific vendor implementation.

The client bundle could statically obtain a service instance like this

String filter = "(service.vendor=JBoss*)";
String sname = AccountService.class.getName();
ServiceReference[] srefs = context.getServiceReferences(sname, filter);
AccountService service = (AccountService)context.getService(srefs[0]);

Please note that the client explicitly defines a filter criteria for available services. In this case the specific service vendor.

To account for the dynamic nature of OSGi services, the client would not want to make the assumption that the AccountService is already available when its BundleActivator.start() method is invoked by the framework. This assumption would create an implicit start order dependency of the implementation and client bundle (i.e. the implementationbundle must be started before the client bundle).

If the the client can dynamically react to the AccountService coming and going it should use code like this

String sname = AccountService.class.getName();
String filterstr = "(&(objectClass=" + sname + ")(service.vendor=JBoss*))";
Filter filter = FrameworkUtil.createFilter(filterstr);

// Track the service
ServiceTracker tracker = new ServiceTracker(context, filter, null)
{   
   public Object addingService(ServiceReference reference)   
   {
      service = (AccountService)super.addingService(reference);
      return service;
   }

   public void removedService(ServiceReference reference, Object tracked)
   {
      super.removedService(reference, tracked);
      service = null;
   }
};
tracker.open();

The dynamic nature of OSGi services is in fact its real beauty. The client gets informed when the service gets modified (i.e. a new version of the service from the same vendor becomes available)

Auto registration of META-INF/services

David Bosschaert, a friend and colleague recently brought the topic of META-INF/services and its prevalent nature in Java component architecture to my attention. Because it is such a common pattern, I investigated how an OSGi framework could reuse the information that is already present in META-INF/services when a non OSGi component library is wrapped or migrated to be used in an OSGi framework.

Starting with JBoss OSGi 1.0.0.Beta6, which should come out early Feb-2010, we provide a jboss-osgi-serviceloader bundle. That registers a LifecycleInterceptor to introspect every installed bundle. Should a bundle contain META-INF/services, these services are automatically registered as ServiceFactories.

The interceptor associates the following properties with every service it registers
Have a look at the service loader example that demonstrates this functionality

Running org.jboss.test.osgi.example.serviceloader.ServiceLoaderTestCase
Tests run: 2, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 7.415 sec

Conclusion

The OSGi service model has much to offer over the traditional Java model of interfaces and service factories. The ServiceLoader being a particular and well known implementation of the more general service factory pattern.

In OSGi you can have multiple implementations of the same service available in the framework. Services can be associated with arbitrary meta data, which means you can version your public contract. Clients can use a meta data filter to select a particular implementation. Implementation, can be strictly separated from API with no back doors left open.

Lastly a few items from my OSGi best practises catalogue
  • Export the public contract, not the implementation
  • Declare implementation details in Private-Package
  • Associate specific versions with your public API packages
  • Define dependencies on package versions, not bundle versions
  • Define which version range of the API a you implement
  • Separate the API bundle from the implementation bundle
  • Keep you public contract concise, well documented and simple
  • Components should interact with services, not arbitrary stuff you export
  • Only service interfaces and their associated types should be exported
  • Track services dynamically, instead of getting the service from the framework

May this be useful

3 comments:

  1. Great article! Of course, it's easy enough to write a standard extender bundle to automatically register classes declared in META-INF/services/ as OSGi services, but it's a bit harder to support clients that want to use java.util.ServiceLoader to obtain instances of services...

    Incidentally, the way you describe "Private-Package" is a little incorrect. Private-Package is actually not an OSGi header at all, and it will be ignored by the OSGi runtime. The "private" packages of your bundles are simply the ones that are present in the bundle but not listed in the Export-Package header. In other words, all packages are private by default, and only those that are explicitly exported (via Export-Package) are available for importing by other bundles.

    "Private-Package" is actually an instruction used by Bnd, the build tool you used. It simply tells Bnd which packages to include in the bundle when it is built.

    ReplyDelete
  2. Yes, you are absolutely right. Making ServiceLoader work for clients is not trivial at all (if not impossible AFAIK). Let me add that angle to problem - I actually wanted to do that anyway.

    I'll also fix the references to Private-Package. Assuming that it is a recognized header is indeed missleading.

    ReplyDelete