Modular design and deployment with Spring

Introduction

There has been a long quest for module based design, where everyone wish they could simply unzip and drop new packaged code into an application folder and simply have it work. Some go as far as wishing no application restart required, others content with just a restart. No configuration changes, no conflicts.

This article will go through the following steps, incrementally improving our design. The first step is showing off the good (or bad?) old days, on how we could do an application in a modular design fashion. The next step would be introducing spring, adding the modules by changing a single application configuration file. The final step would involve no configuration file changes when adding new modules.

Initial Design

The application in mind is simply one that is a series of modules, each with a print method. The print method will print out the module class name, and the application has a print method that iterates the modules and invoke each module's print method. Simple task but sufficient for our purpose.

Below are the code listing. To keep things compact I have removed comments and newlines, assertions, checks from the source.


// Module.java
package step1;
import java.io.*;
public interface Module {
    void print(final PrintStream out);
}
// Application.java
package step1;
import java.io.*;
import java.util.*;
public final class Application {
    private final List<Module> modules = new ArrayList<Module>();
    public final void addModule(final Module module) 
        
    
    public final void print(final PrintStream out) {
        for(final Module module : modules) 
            
        
    }
}
// GoodByeModule.java
package step1;
import java.io.*;
public final class GoodByeModule implements Module {
    public final void print(final PrintStream out) 
        
    
}
// HelloWorldModule.java
package step1;
import java.io.*;
public final class HelloWorldModule implements Module {
    public final void print(final PrintStream out) 
        
    
}
// EntryPoint.java
package step1;
public final class EntryPoint {
    public final static void main(final String[] args) {
        final Application application = new Application();
        application.addModule(new HelloWorldModule());
        application.addModule(new GoodByeModule());
        application.print(System.out);
    }
}

In the old days, module creation and addition are done within the application itself. Hardly a friendly design. But it is a first step. Running the application prints out the expected output.

HelloWorldModule
GoodByeModule

Spring Enabling

The next step would be to spring enable them. Let us move baby steps at a time. Instead of creating the application and modules ourselves, we will create them from a spring factory. The objects themselves will be configured in configuration xml files. For the purpose of the future, I will have three spring configuration files. application.xml, helloworld.xml, and goodbye.xml. These files reside in a config folder, which the application will scan for and load them.

The main changes are in EntryPoint.java, as well as addition of three configuration files. Thus I will list only them. (Note that I did make a copy and change the package name to step2 instead of step1.

// EntryPoint.java
package step2;
import java.io.*;
import org.springframework.context.*;
import org.springframework.context.support.*;
public final class EntryPoint {
    public final static void main(final String[] args) {
        final ApplicationContext context = new FileSystemXmlApplicationContext(getFiles("./config/step2/"));
        final Application application = (Application)context.getBean("application");
        application.addModule((Module)context.getBean("helloworld"));
        application.addModule((Module)context.getBean("goodbye"));
        application.print(System.out);
    }
    private final static String[] getFiles(final String path) {
        final String[] results = new File(path).list(new FilenameFilter() {
            public final boolean accept(final File f, final String s) {
                return s.endsWith(".xml");
            }
        });
        for(int i = 0; i < results.length; ++i) {
            results[i] = path + results[i];
        }
        return results;
    }
}
<?xml version="1.0" encoding="UTF-8"?>
<!-- application.xml -->
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd">
    <bean id="application" class="step2.Application" />
</beans>
<?xml version="1.0" encoding="UTF-8"?>
<!-- helloworld.xml -->
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd">
    <bean id="helloworld" class="step2.HelloWorldModule" />
</beans>
<?xml version="1.0" encoding="UTF-8"?>
<!-- goodbye.xml -->
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd">
    <bean id="goodbye" class="step2.GoodByeModule" />
</beans>

The results are still the same when ran. So I will omit the results now.

Adding Modules via Spring

Moving on to the next step, instead of adding modules in code, I will add them via the configuration. Dependency injection is performed on setter methods. Therefore I will have a setModules method in application, and then inject the modules with a list of references to other beans.

I will only list the changed method 'main', Application class, as well as the application.xml file below. Remember again, the package name has changed to step3.

// Application.java
package step3;
import java.io.*;
import java.util.*;
public final class Application {
    private List<Module> modules = new ArrayList<Module>();
    public final void setModules(final List<Module> modules) {
        this.modules = modules;
    }
    public final void print(final PrintStream out) {
        for(final Module module : modules) 
            
        
    }
}
// EntryPoint.java
public final static void main(final String[] args) {
    final ApplicationContext context = new FileSystemXmlApplicationContext(getFiles("./config/step3/"));
    final Application application = (Application)context.getBean("application");
    application.print(System.out);
}
<!-- application.xml -->
<bean id="application" class="step3.Application">
    <property name="modules">
        <list>
           <ref bean="helloworld" />
           <ref bean="goodbye" />
        </list>
    </property>
</bean>

This is much better. We no longer configure the modules in code. Configurations are all done in the configuration xml files.

A Step Furthur

Most people would stop here. Satisfied. Yet adding of new modules still require changing the configuration file for Application. And for scenarios where modules could be added and removed anytime, it is a manual change (you cannot simply drop a new version of the application.xml and overwrite the existing ones because you cannot be sure what modules were already included!)

So can we do better? Sure we can. We simply have to rethink our concept and take advantage of the autowire of Spring!

Our current concept is that the Application class adds modules. It is in charge of knowing what modules there are.

But let us reverse our thinking a bit. We could have modules look for the only Application instance, and have them register themselves!

Armed with this new concept (well it is not exactly new, but it is a change in thinking from the previous step), we add a method setApplication to each module. When the module is set with an application, it simply invokes the addModule method.

Another change we have to make is set the autowire attribute of the beans to byType. If it is byName (the default), it would need to know the name of the Application bean class. This is fine, but we want to make it completely transparent, right?

We will revert back to the implementation of Application in step2, but renaming it as ApplicationImpl. We will introduce a new interface Application, which ApplicationImpl will implement. It will have the abstract method addModule, as well as print. Next we will change the implementation for Module to be an abstract class instead of interface, with a setApplication method.

Below is the complete listing

// Module.java
package step4;
import java.io.*;
public abstract class Module {
    public void setApplication(final Application application) 
        
    
    public abstract void print(final PrintStream out);
}
// Application.java
package step4;
import java.io.*;
public interface Application {
    void addModule(final Module module);
    void print(final PrintStream out);
}
// EntryPoint.java
package step4;
import java.io.*;
import org.springframework.context.*;
import org.springframework.context.support.*;
public final class EntryPoint {
    public final static void main(final String[] args) {
        final ApplicationContext context = new FileSystemXmlApplicationContext(getFiles("./config/step4/"));
        final Application application = (Application)context.getBean("application");
        application.print(System.out);
    }
    private final static String[] getFiles(final String path) {
        final String[] results = new File(path).list(new FilenameFilter() {
            public final boolean accept(final File f, final String s) {
                return s.endsWith(".xml");
            }
        });
        for(int i = 0; i < results.length; ++i) {
            results[i] = path + results[i];
        }
        return results;
    }
}
// ApplicationImpl.java
package step4;
import java.io.*;
import java.util.*;
public final class ApplicationImpl implements Application {
    private final List modules = new ArrayList();
    public final void addModule(final Module module) 
        
    
    public final void print(final PrintStream out) {
        for(final Module module : modules) 
            
        
    }
}
// HelloWorldModule.java
package step4;
import java.io.*;
public final class HelloWorldModule extends Module {
    @Override
    public final void print(final PrintStream out) 
        
    
}
// GoodByeModule.java
package step4;
import java.io.*;
public final class GoodByeModule extends Module {
    @Override
    public final void print(final PrintStream out) 
        
    
}
<?xml version="1.0" encoding="UTF-8"?>
<!-- application.xml -->
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd">
    <bean id="application" class="step4.ApplicationImpl" />
</beans>
<?xml version="1.0" encoding="UTF-8"?>
<!-- helloworld.xml -->
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd">
    <bean id="helloworld" class="step4.HelloWorldModule" autowire="byType" />
</beans>
<?xml version="1.0" encoding="UTF-8"?>
<!-- goodbye.xml -->
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd">
    <bean id="goodbye" class="step4.GoodByeModule" autowire="byType" />
</beans>

When we run the program, however, we get a slightly different result.

GoodByeModule
HelloWorldModule

Conclusion

I am unsure what the sequence of bean creation/invokation is, but for our purpose of modular design, this is satisfactory.

With this new approach, you could create really isolated modules. Adding and removing new modules did not require changing the main application configuration file at all. Configurations are truly modular based as well.

This shift in thinking will enable us to apply it in other scenarios as well. Listeners to a single process can be configured in files as well. The sequence of invokation can be solved as well, theoritically. Each module *could* be given a priority number, and the application could invoke them sorted by priority.

However, this does not work if your 'listeners' have to listen to more than one source. The autowire would complete about duplicate dependencies, and the application will come to a halt. Maybe I would think of how to solve that next...