Monday, January 30, 2012

Haven't you heard of Service Provider Interface?

ServiceLoader javadoc states that:
"A service is a well-known set of interfaces and (usually abstract) classes. A service provider is a specific implementation of a service. The classes in a provider typically implement the interfaces and subclass the classes defined in the service itself. Service providers can be installed in an implementation of the Java platform in the form of extensions, that is, jar files placed into any of the usual extension directories. Providers can also be made available by adding them to the application's class path or by some other platform-specific means."
Every Java developer that may call himself a Java developer is obliged to know this!

Our story goes like this...
In the year of our lord two thousand and six, I was working in a project revolving around building Digital Libraries based on the SOA paradigm. One of my tasks was to build a pluggable execution engine, that is, first define a formal language that would describe the business process along with an API of the execution engine.  There should also be a monitoring service along with statistical analysis component. These artifacts would be independent of the actual implementation, in a JNDI-like manner.

Please, don't start wondering: hey, why didn't use something standardized like BPEL?

A suitable Design Pattern for this is the Abstract Factory Pattern (see here and here). Here is a simplified version of the UML diagram:
UML Diagram - Abstract Factory with two factory concretizations
As shown above, the starting point is the ExecServiceFactory which is an abstract class that exposes the basic set of business objects (interfaces), namely StatisticsService, MonitoringService and ExecEngine.

There are two implementations of this set, an internal execution service and an adaptor to an existing BPEL engine. The entry points to these concretizations are InternalExecServiceFactory and BPELExecServiceFactory respectively. These factories extend the ExecServiceFactory and provide implementations for the abstract operations getExecEngine, getMonitorService and getStatisticsService. These operations return implementations of the StatisticsService, MonitoringService and ExecEngine interfaces.

So, the first step was easy. The only thing that I had to do is register the two implementations and dynamically select one of them at runtime. It shouldn't be hard. Well... for a novice programmer like myself at that time (many would argue that this is still the case) it is hard , for a single reason: extensibility. But first things first...

The business requirement was to choose one implementation based on some criteria,. To tackle this issue every implementation should provide:
satisfyCriteria(Set<Criterion> criteria) :: Score 
 so that the highest scoring implementation should be selected at runtime based on the given criteria.

So, how should I automatically register the implementations so as to run through them and employ the highest scoring one at runtime? There are two solutions:
  1.  The registry component should be aware of the implementations.
  2.  The implementations should be aware of the registry  and register themselves.

Solution 1 is quite simple. If the registry component has knowledge of the extending factories then it only has to store the corresponding classes in a list and iterate over them at runtime, so as to select the highest scoring implementation. But this has the obvious disadvantage of extensibility. What does this mean? If you want to add a new implementation then you have to change the existing registry code.

Solution 2 is easier. At class initialization time, the implementation simply invokes a registerMe method of the registry component. But there's a small catch. How/When is a class initialized?

According to Java Programming Language Concepts:
Initialization of a class or interface consists of invoking its static initializers (§2.11) and the initializers for static fields (§2.9.2) declared in the class. This process is described in more detail in §2.17.4 and §2.17.5.
A class or interface may be initialized only as a result of:
  • The execution of any one of the Java virtual machine instructions new, getstatic, putstatic, or invokestatic that references the class or interface. Each of these instructions corresponds to one of the conditions in §2.17.4. All of the previously listed instructions reference a class directly or indirectly through either a field reference or a method reference. Upon execution of a new instruction, the referenced class or interface is initialized if it has not been initialized already. Upon execution of a getstatic, putstatic, or invokestatic
    instruction, the class or interface that declared the resolved field or method is initialized if it has not been initialized already.
  • Invocation of certain reflective methods in the class library (§3.12), for example, in class Class or in package java.lang.reflect.
  • The initialization of one of its subclasses.
  • Its designation as the initial class at Java virtual machine start-up (§5.2).
Prior to initialization a class or interface must be linked, that is, verified, prepared, and optionally resolved.
This basically means that an entity must somehow refer to that class. But we don't want that... So, solution 1 is the only way.

Back in 2006 I followed that solution. The registry component was embedded in the ExecServiceFactory and loaded the implementation set from a .properties file. Hard-coded solution but it worked.

In the project that I worked in we employed an integration tool called ETICS (see here).
...it provides a service to help software developers, managers and users to better manage complexity and improve the quality of their software. The service allows to fully automate the way a software is built and tested. It provides "out-of-the-box" build and test system, powered with a product repository.
ETICS was working fine but it had a serious disadvantage (at least at that time)... it was damn complex and not fun-to-play-with. It required lots of time to add a new component, so you can understand that all developers wanted to have the smallest possible interaction with it.

We had nightly builds and periodic release cycles (every 1-2 months). These cycles were quite inflexible and once the cycle ended then you had to wait for the next cycle to start before adding a new component or making any changes to the existing ones.

The problem begun when I needed to add a new implementation. Creating a new configuration wasn't much of a problem. Forgetting to add a new configuration version for the ExecServiceFactory component is totally another issue!

Adding a new component meant changing the properties file that enumerated the existing implementations and lied within the ExecServiceFactory component. So I had to create a new version of this component and provide a corresponding ETICS configuration for this. In short, I've missed the cycle and the new feature wasn't delivered according to schedule...

Had I known Java's Service Provider Interface (SPI) would save me much trouble.

So what is this SPI thing? Simply put, it's a methodology/mechanism of creating extensible Java applications. An extensible application is one that you can extend easily without modifying its original code base. You can enhance its functionality with new plug-ins or modules. Developers, software vendors, and even customers can add new functionality or application programming interfaces (APIs) by simply adding a new Java Archive (JAR) file onto the application classpath or into an application-specific extension directory.

Here, here, here and here you may find all the necessary information. All you have to do is create a new implementation (or service provider according to the SPI terminology) packaged in a new JAR, create a new file under META-INF/services directory with the name of the interface class (ExecServiceFactory in our case prefixed with the package name) and just place the implementation class full name in it (e.g. mypkg.InternalExecServiceFactory). Then, in the registry code, you can iterate through the available implementations using
 private static ServiceLoader<ExecServiceFactory> esFactoryLoader
     = ServiceLoader.load(ExecServiceFactory.class);
The selection of the highest scoring implementation, based on a given criteria, is accomplished by this piece of code.
 public ExecServiceFactory getImpl(Set<Criterion> criteria) {
     Score tScore = Score.MIN_VALID;
     ExecServiceFactory ret = null;
     for(Iterator<ExecServiceFactory> it = loader.iterator();it.hasNext();) {
         ExecServiceFactory cp = it.next();
         Score score = cp.satisfyCriteria(criteria);
         if(score<tScore) {
             ret = cp;
             tScore = score
         }
     }
     return ret;
 }
So... no code change, only jar addition. And that's all! SPI is widely used in may Java technologies, such as JDBC and JNDI and quite frankly I should have been aware of it... Anyway, I learned it some months later. Better late than ever!
"Ignorance is the curse of God; knowledge is the wing wherewith we fly to heaven" - William Shakespeare

No comments:

Post a Comment