The latest episode of the JavaPosse podcast presents a session of the this year’s JavaPosse roundup on Scala and application modularity. Although I attended the conference I did not attend this session, which is rather sad in retrospect as the attendees discuss a lot of things I also deal with.
As a note aside I want to mention, that I am of course aware of OSGi and it’s capabilities, that prvide a very sophisticated and mature solution to this problem. Nevertheless using OSGi introduces a fair amount of additional complexity and - despite its maturity - still lacks tool support to a large extend. Thus I won’t discuss anything OSGi related here.
Bill Venners opens the session by describing how Scala’s traits can be used to mix in functionality into objects at their construction time and wants to know how this can help solving problems regarding modularity in currently in-the-field Java. As I am facing these kinds of issues in my everyday development I will briefly describe the solution I came up with.
At some point Joel Neely brings the discussion to an interesting point. There is an example the group elaborates on dealing with a piece of software that contains a certain part with customer specific code that will change for each deployed instance of that software. A second example they discuss is somewhat similar but deals with tax calculating rules in various US states that can differ from state to state.
Both examples should be factored out into modules as I think, but the distinction which of the modules shall be used by the application has to be made at different times. Joel uses the term “binding time” to describe this situation. In the case of the customer specific code binding time is application packaging time actually. Stepping one step back it boils down to the availability of the module JAR in the applications classpath. Using a dependency management system like Maven you actually would have a Maven project for customer A that declares a dependency to the application core plus the JAR with customizations for customer A and the same for customer B but with a different customizations JAR. This way you also avoid shiping customer specific code to a different customer.
Besides the availability of types, organizing the object web an application consists of is the second part of the challenge. Spring serves this purpose quite well and supports a variety of configuration formats and options. A first step to real application modularity is aligning
ApplicationContext creation to the before mentioned usage of the classpath to assemble modules.
Springs XML configuration is widely regarded as very static as typically changing something in the bean wiring means editing some of the config files. But using
<import resource="classpath*:com/acme/**/my-context.xml" />
in config files of a core application allows to transparently add beans from configuration files named
my-context.xml under a package
com.acme to an
ApplicationContext. This way determining the application classpath leads to the creation of certain Spring beans without actually touching any configuration files as long as module JARs declare their beans in
my-context.xml and thus aligns availability of beans in the
ApplicationContext to the classpath configuration.
This leads us to the question how an extensible object gets access to the instance that is created from an module JAR’s
my-context.xml. Referencing by bean id does not seem to be a good idea as this would imply to much on the module author and entirely break if there is more than one bean to be referenced. This is where Hera comes into play. It provides a Spring namespace to dynamically lookup beans from an
ApplicationContext and let them be bound to a collecting bean. Thus by defining
<hera:list id="foo" class="my.plugin.Interface" />
all beans implementing
my.plugin.Interface would be collected into a
ListFactoryBean and could be referenced by other beans. In effect this will allow you to dynamically inject dependency in your beans just by configuring your classpath. For our customer sample you would create two modules with classes implementing
Interface and use your build system to package one into the one assembly the other one into the other. No Spring reconfiguration needed - configure your app by stating it’s dependencies.
As this serves fine for our customer code example what about the taxes example. To find a suitable answer for this problem you actually would have to add all the JARs for the different states and decide at runtime which one to use dependeing on the state the user originates from. Here’s how you’d approach this using Hera. The first step is to let your
TaxCalculator extend Hera’s
Plugin<T> interface, that adds a
supports(T delimiter) method to allow determining if the plugin is suitable in a given context. The delimiter type in our example could be a
State enum or the
User type itself. Thus the plugin itself can decide which states it provides tax calculation for.
At the client side aquiring a simple list of all the
TaxCalculator instances is not sufficient anymore. You probably want to gain a little more detailed access to certain instances. Thus instead of the list element you now use a
<hera:registry id="foo" class="my.plugin.TaxCalcuator" />
to obtain an instance of
PluginRegistry<TaxCalculator, User> that allows you to access the plugin instance in various way, returning defaults if no plugin found for given delimiter and so on. See the JavaDoc for details.
Now suppose you have a set of tax modules for the states as well as some for european countries. Then you can “configure” one instance of your application by declaring customer A customizations JAR as well as the US tax jars in the pom.xml for customer A. The same applies to customer B where you might choose the set of european tax jars.
OSGi of course is still the way to go if you’re looking for a really sophisticated Java module system. If you are not willing or able to bear the increased complexity and can live without runtime swapping of JARs and visibility constraints enforced at runtime the combination of Maven, Spring and Hera provides a powerful development environment for modular Java applications.