eXo services container

What it is?

The services container is a wrapper on a pico container instance so as to provide an Inversion of Control (IoC) services manager. Therefore, the services are not responsible for the instantiation of the components they depend on. This architecture provides a loosely coupled design where dependant services implementation can be transparently exchanged. The singleton design pattern is used to make it available from all over the platform using :

ServicesManager.getInstance()

IoC / Dependency injection in 3 minutes...

This pattern has several names :

  • Hollywood principle : “don't call me, I will call you”
  • Inversion of Control
  • Dependency injection

The Inversion of Control design pattern really makes the developer's life much simpler and forces you to use interfaces instead of classes. This produces a much cleaner and maintainable code. The first aim of the pattern is not to let the object create itself the instances of the object it references. This job is delegated to the container (assembler in the picture).

Before :

After :

There are three ways to inject the implementation in the dependant object :

  • Constructor injection
  • Setter injection
  • Use of service locator
The eXo platform uses the constructor injection when the object can be a component of the service container and the service locator when it can not be added to the container (this is the case for objects like AspectJ aspect or servlet listeners).
             public ServiceA(ServiceB serviceB) {...}

            and

             ServicesManager.getInstance().getService(ServiceB.class)
          

One of the main advantage of the IoC containers is to provide a simple way to unit tests your component. Indeed, by just registering a mock component implementation into pico in the setUp() method of a JUnit test, you can test the object easily outside the scope of any framework, server or anything like makes unit testing complex.

For more information read the following article from Martin Fowler site.

An example

Let's look at an example : imagine an organization service that manages users, groups and memberships between them. It needs a connection to the Hibernate services in order to persist the data. As we use Pico Container, the dependency must be declared in the OrganizationService constructor.

...
public OrganizationService {

  private HibernateService service;

  public OrganizationService(HibernateService service) {
    this.service = service;
  }

  //methods that use the HibernateService interface
  ...
}
        

It is highly advisable that the constructor uses interface types instead of classes implementation. Indeed you can register into any pico-container the class implementation and then, when the object is instantiated pico can resolve the implementation of the interface that the constructor needs. Therefore, just by changing the implementation registered into pico you can completely modify the behaviour of your object without any change in it.

Retrieving a service

A service can be retrieved from the constructor of a container's componenent like in the previous example.

It is also possible to use the service locator when the object can not be a component itself.

(PortletMonitor)ServicesManager.getInstance().getService(PortletMonitor.class);

Adding a service

There is two ways to add a service :

  • from an XML configuration file
  • using a service manager method (or util class) at runtime

The configuration schema of the XML conf file is the following one :

The implementation of each service can be packaged as an independent JAR archive with an exo-service.xml file bundled with it. This XML file defines the classes to be registered in our ServiceContainer object :

          
<?xml version="1.0" encoding="ISO-8859-1"?>

<services>
  <name>DatabaseService</name>
  <service>
    <description>Database service</description>
    <class-name>exo.services.database.impl.DatabaseServiceImpl</class-name>
  </service>
  <service>
    <description>Hibernate service</description>
    <class-name>exo.services.database.impl.HibernateServiceImpl</class-name>
  </service>
</services>
          
          

Each given class name represented is simply an implementation of one of the service API interfaces. When the ServiceManager singleton object is called for the first time it searches for all the exo-services XML file located in the classpath and register the service implementation class into a pico-container instance. With this automated discovery mechanism you only have to replace the implementation JAR to change the concrete implementation, the ServiceManager and pico-container takes care of the rest.

public class ServicesManager {

  private static ServicesManager ourInstance;
  private DefaultPicoContainer container;
  private Map servicesContext;

  public static ServicesManager getInstance() {
    if (ourInstance == null) {
      synchronized(ServicesManager.class) {
        ourInstance = new ServicesManager();
        if (Environment.getInstance().getPlatform() != Environment.STAND_ALONE){
          ourInstance.installServices() ;
        }
      }
    }
    return ourInstance;
  }

  private ServicesManager() {
    servicesContext = new HashMap();
    container = new DefaultPicoContainer();
  }

[...]

  private void installServices() {
    try {
      ClassLoader cl = Thread.currentThread().getContextClassLoader() ;
      JAXBContext jc = JAXBContext.newInstance("exo.services.model");
      Unmarshaller u = jc.createUnmarshaller();
      Enumeration e = cl.getResources("exo/services/exo-service.xml") ;
      while(e.hasMoreElements()) {
        URL url  = (URL) e.nextElement() ;
        InputStream serviceDescriptor = url.openStream();
        Services services = (Services) u.unmarshal(serviceDescriptor);
        ServiceContext serviceContext = new ServiceContext( cl, services);
        addService(serviceContext);
      }
    } catch (Exception ex) {
      ex.printStackTrace() ;
    }
  }
}
        

To add a service programatically you can use the following Util class :

      ServicesUtil.addService("LogService", null, "exo.services.log.impl.LogServiceImpl",
			      getClass().getClassLoader());
        

Let's talk about a real example, our PortletContainerService is based on the HibernateService; they are also both are based on the LogService. All these services have well defined API interfaces which are used by the other dependent classes. Therefore if you have a log4j implementation of the LogService and that you would like to use a commons-logging one, you just have to change the LogService implementation, bundle that with an XML file that defines the new class to register in the ServiceManager in a JAR archive, and finally deploy it in the application server instead of the previous JAR. No other modifications are necessary in any other services that depends on the log one. And this is the case for all other services!