Container Independence
by kirk knoernschild
Statement
Modules should be independent of the runtime container.
Description
Modules with excessive runtime container dependencies are heavyweight modules that cannot execute outside the confines of the runtime container. A good example of a heavyweight technology is Enterprise JavaBeans (EJB), and the meteoric rise in popularity of lighter weight frameworks, such as Spring, are the direct result of the shortcomings of heavyweight technologies. While lightweight modules are not container dependent, they are still able to leverage the infrastructure capabilities (e.g., security, transactions) of the container. Lightweight modules with no container dependencies have two significant advantages.
- They are portable across runtime environments. For instance, a lightweight module can be deployed in a Java EE environment, but it can also run directly atop the JVM. Or, a module might be designed to work in a runtime supporting OSGi, but it can also function if an OSGi framework isn’t available.
- The are testable. Because they lack environmental dependencies, they can be tested in isolation, outside the confines of the environment to which they’ll ultimately be deployed.
Additionally, it’s easy to argue that lighter weight modules are also easier to maintain, since their contents aren’t polluted with excessive dependencies. Developing light weight modules demands that container dependencies be abstracted away. Figure 1 illustrates how the client.jar module is made independent of the runtime.jar through the use of the abstraction.jar module, which encapsulates the container dependencies and coordinates the interaction of client.jar module in the runtime environment. As a result, the Client class will have no dependencies on the runtime.jar nor the abstraction.jar modules’ APIs.

Figure 1: Container Independence
Implementation Variations
External configuration can be used to configure a module so that it operates correctly in a specific runtime environment. In these situations, External Configuration is a pattern that is used to achieve Container Independence since the configuration dependencies are moved to an external configuration file. A superceding framework then manages module configuration. However, there are subtle differences between External Configuration and Container Independence that validates pattern individuality. External configuration is used to configure a module so that it can be used across contexts. Container Independence is leveraged to ensure a module is portable across containers.
For instance, configuring a module with a userid and password combination so that it can be used to access different database instances is an example of external configuration, not container independence. While poor practice, hardcoding the userid and password combination wouldn’t create dependencies on any runtime container, though obviously would limit the context in which the module could be reuse.
In many cases, developers don’t need to roll their own code to abstract away the container dependencies. Instead, a framework is used. The most common mechanism to achieve container independence is through dependency injection, where a module’s dependencies are injected at runtime. Many dependency injection frameworks also provide important infrastructure capabilities that help ensure the module remains container independent. For instance, in the example below that uses Spring DM, the framework provides dependency injection capabilities but also provides critical functionality surrounding OSGi service management.
Consequences
Container Independence significantly reduces the amount of infrastructure code a developers needs to write. While this provides significant benefits that enable testing and increases portability, it can also result in complex infrastructure code that is complex to write and manage. When a dependency injection framework is used, xml configuration files tend to proliferate. Fortunately, the tradeoff is often worth the benefit.
Sample Code
Let’s consider two very simple modules – a client and a service, as illustrated in Figure 1. Also shown is a configuration file that will be used to help us realize container independence.

Figure 1 – Simple Client Module Using a Service Module
Listing 1 illustrates the start method of a class in the service module that is registered as an OSGi service, enabling other classes to discover the class when requesting the service. As can be seen, the code is heavily dependent on the OSGi framework packages, and this prohibits the code from being used outside the context of an OSGi runtime.
import java.util.Properties;
import com.extensiblejava.hello.service.HelloService;
import org.osgi.framework.BundleActivator;
import org.osgi.framework.BundleContext;
import org.osgi.framework.ServiceListener;
import org.osgi.framework.ServiceEvent;
import org.osgi.framework.ServiceRegistration;
public class HelloServiceImpl implements HelloService, BundleActivator {
private ServiceRegistration registration;
public void start(BundleContext context) {
Properties props = new Properties();
props.put("Language", "English");
registration = context.registerService(HelloService.class.getName(), this, props);
}
public String sayHello() {
return "Hello World!! ";
}
public String sayGoodbye() {
return "Goodbye World!!";
}
}
Listing 1: Heavy Container Dependencies
Any client that hopes to use this class that has been registered with the OSGi service registry also needs to leverage the OSGi framework. Listing 2 illustrates a sample client in the client module that interacts with OSGi to obtain a reference to the service registered in Listing 1.
package com.extensiblejava.hello.client;
import com.extensiblejava.hello.service.HelloService;
import org.osgi.framework.BundleActivator;
import org.osgi.framework.BundleContext;
import org.osgi.framework.ServiceReference;
import org.osgi.util.tracker.ServiceTracker;
public class HelloConsumer implements BundleActivator {
private ServiceTracker helloWorldTracker;
private HelloService helloService;
public void setService(HelloService helloService) {
this.helloService = helloService;
}
public void removeService() {
this.helloService = null;
}
public void start(BundleContext context) throws Exception {
helloWorldTracker = new ServiceTracker(context, HelloService.class.getName(), null);
helloWorldTracker.open();
HelloService hello = (HelloService) helloWorldTracker.getService();
if (hello == null) {
System.out.println("Hello service unavailable on HelloConsumer start");
} else {
System.out.println(hello.sayHello());
}
}
public void stop(BundleContext context) {
HelloService hello = (HelloService) helloWorldTracker.getService();
if (hello == null) {
System.out.println("Hello service unavailable on HelloConsumer stop");
} else {
System.out.println(hello.sayGoodbye());
}
helloWorldTracker.close();
}
}
Listing 2: Heavyweight Client
The heavyweight nature of this code prevents it from being used outside the context of the OSGi framework, and also prohibits unit testing. Keeping our exact same module structure as illustrated in Figure 1, we leverage Spring DM to remove all dependencies on the OSGi framework. Listings 3 and 4 show the updated code that leverages Spring DM, illustrating all OSGi dependencies have been removed. To achieve this, we’ve needed to deploy the Spring DM modules to the OSGi runtime, and have also introduced the appropriate configuration file that informs Spring DM how to treat these modules. The configuration file is shown in Listing 5.
package com.extensiblejava.hello.service.impl;
import java.util.Properties;
import com.extensiblejava.hello.service.HelloService;
public class HelloServiceImpl implements HelloService {
public String sayHello() {
return "Hello OSGi Spring World!! ";
}
public String sayGoodbye() {
return "Goodbye OSGi Spring World!!";
}
}
Listing 3: Independent HelloServiceImpl Class
package com.extensiblejava.hello.client;
import com.extensiblejava.hello.service.HelloService;
public class HelloConsumer {
private HelloService hello;
public void setHelloService(HelloService hello) {
this.hello = hello;
}
public void start() throws Exception {
System.out.println(hello.sayHello());
}
public void stop() throws Exception {
System.out.println(hello.sayGoodbye());
}
}
Listing 4: Independent Consumer Class
<osgi:reference id="helloService" interface="com.extensiblejava.hello.service.HelloService"/> <bean name="helloConsumer" class="com.extensiblejava.hello.client.HelloConsumer" init-method="start" destroy-method="stop"> <property name="helloService" ref="helloService"/> </bean>
Listing 5: Spring XML Configuration File
Wrapping Up
Container dependencies result in heavyweight modules that are difficult to reuse and maintain. Eliminating these dependencies helps improve the ease with which modules can be tested. Modules with no container dependencies are also portable across runtime environments and can be used in a variety of different contexts. Dependency injection is a common technique used to help ensure modules aren’t container dependent.