A: When you can’t load it into the JVM without it hitting the DB
I am working on a large and somewhat aged app that has an interesting pattern.
Our services are Singletons (yes, I know, Singletons are Evil(TM) ) are implemented like this:
public interface Foo {
public static final Foo INSTANCE = (Foo) Plugins.load(Foo.class);
void doSomething();
}
The Plugin class statically initialises the instance by looking it up in a Properties file, something like this:
public class Foo {
public static Object load(Class clazz) {
String implClassName = getProperty(clazz.getName());
try {
Class implClass = Class.forName(implClassName);
return implClass.newInstance();
}
catch(Exception ex) {
logger.log("Could not load class " + implClassName, ex);
}
}
}
The properties file looks something like:
#interface=impl
com.demo.Foo=com.demo.impl.AConcreteFoo
The idea is that you can change the services depending on the location, server, whether you’re doing tests etc. So far, so nice.
But there’s a problem. Actually a few.
We also have a class that loads codes from a db table - through a statically loaded plugin. So what happens if I do something like:
public void testSomethingThatUsesAFoo() {
Mock mockFoo = mock(Foo.class);
...
}
Well Foo’s class is loaded by the classloader. It goes off and runs all the static initializers. And one of those calls into another service, static loaded and initialized, which loads the database, reads in all the codes, etc. so before you know it, just by importing an Interface, you have a test that requires the real database to be present. You can’t run the unit tests without a db, you can’t take your laptop home and do some refactoring. Nasty.
Worse is the fact that the error that is thrown by the plugin loader is obscured by a zillion ClassNotFoundExceptions, since the Plugin load failure, being in a static initializer, means the JVM cannot load the class.
Also since the exception is not centrally caught, it is also not possible to know when the system is in a fully configured and running state - there may be a Plugin that will not load until daily processing runs at 3AM next Wednesday - at which point that component fails in an isolated and undetected way. So the system keeps running while part of it is completely broken. Again - ouch.
From a testability standpoint, since the INSTANCE is defined as public static final, we can’t swap in a different implementation during test without running the tests in a different VM, with a different plugins.properties. Fine (in fact desirable) for Integration tests, but a real pain for unit tests.
So what’s the solution? We could just make each of the Foo users directly call (Foo) Plugins.load(Foo.class), but that doesn’t really help - it still means that the loading of services is dependent on random (or at least obscure) factors such as the order of class loading and static initialization. And it still doesn’t solve the random future failures problem.
The solution we have chosen is to move to a ServiceLocator pattern, backed by PicoContainer (Dependency Injection). We have a ready built pico configuration file in the properties file described above - basically the pico code to set the system up boils down to:
for each property in plugins.properties {
pico.register( Class.forName(property.name), Class.forName(property.value) );
}
pico.start();
We will also be turning static initializers in the implementation classes into start() methods and externalizing their dependencies via their constructors.
Should be interesting.