On the composition of small Java programs

by kengilmer

Typically when writing Java code, we’re integrating into larger systems such as application servers or cloud engine containers.  Or we use dependency injection frameworks that essentially define a large part of how an application is structured.  However there are times when this is overkill and starting fresh is best.  Inspired in part by the ease-of-use of the dropwizard web framework, I have distilled a few elements that when taken together make for a nice starting point for a modern Java-based program. With very little code, an annotation-validated YAML-based configuration class is neatly associated with a Guava Service.

It is useful for a program to have a configuration that lives outside of the code that can be changed.  There are various approaches, but the YAML format lends itself to readability and simplicity.  Jackson and Hibernate Validator are able to turn YAML files into Java configuration object instances quite nicely.  Legal values can be expressed via annotation and the program code can work with the idiomatic Java getters without dealing with the particulars of where and how the file is loaded.  This can cut a out a surprising amount of mundane validation checking.

The relationship between the bootstrap class and the configuration is best if explicitly defined.  This helps to make sense of the context of the configuration.  Too much subclassing can lead to tightly coupled code but in some cases works well to cleanly define such relationships between specific classes.  Once we have a configuration, then comes the creation of the Java classes that will consist of the application logic.  Google’s Guava library has a nice ServiceManager/Service abstraction that provides a nice place to express this startup code.  The ServiceManager is created and passed a set of services to manage.  The Java program defines one or more Services that are the top-level application classes for the program.    Assuming a generic type T that represents the configuration, the following abstract class will parse a YAML file as a Java POJO and pass it to an abstract method that will create Guava services that are then loaded by the Service Manager:

</pre>
public abstract class AbstractBootstrap<T> {
    
    public void run(String[] args, String name, Class<T> configType) throws Exception {
        if (args.length != 1) {
            System.err.println("Usage: " + name + " <configuration.yml>");
            System.exit(1);
        }
        
        ObjectMapper mapper = new ObjectMapper(new YAMLFactory());
        T config = mapper.readValue(new File(args[0]), configType);
        
        final ServiceManager sm = new ServiceManager(getServices(config));
        
        sm.startAsync();
        
        Runtime.getRuntime().addShutdownHook(new Thread() {
            @Override
            public void run() {
                shutdown();
                sm.stopAsync();
            }
        });
    }

    /**
     * Handle any global resource deallocation.
     */
    protected void shutdown() {
    }
    
    /**
     * @param config 
     * @return
     * @throws JAXBException 
     * @throws IOException 
     */
    protected abstract Iterable<? extends Service> getServices(T config) throws Exception;
}

The shutdown method is added for the unfortunate case that there are resources that need to be explicitly managed at a global level.

Now in the actual application, this abstract class is subclassed and the services are created with the configuration. Given a simple configuration file:

mySetting: 1

And corresponding class:

final public class Config {
    private final int mySetting;
    
    @JsonCreator
    public Config(@JsonProperty("mySetting") int mySetting) {
        this.mySetting = mySetting;
    }
    
    public int getMySetting() {
        return mySetting;
    }
}

We can then write the concrete class that will start the application. For my example I’m just printing messages to stdout, but more powerful service options await in the Guava library:

public class BootStrap extends AbstractBootstrap<Config>{

    public static void main(String[] args) throws Exception {
        BootStrap bs = new BootStrap();
        bs.run(args, "Example", Config.class);
    }

    @Override
    protected Iterable<? extends Service> getServices(Config config) throws Exception {
        return Collections.singleton(new MyService());
    }

    private class MyService extends AbstractIdleService {

        @Override
        protected void startUp() throws Exception {
            System.out.println("Hello!");
        }

        @Override
        protected void shutDown() throws Exception {
            System.out.println("Goodbye!");
        }
        
    }
}

From here it’s simply a matter of choosing one of Guava’s Service implementations that match the needs of your program. By using this Guava facility, the lifecycle state is handled for you. Shutdown hooks can be added cleanly at the service level. The Jackson and Hibernate Validation give you clean ways of expressing legal configuration values. Since Jackson does all the deserialization via the constructor, our configuration class remains immutable.

Using Guava Services, Jackson, and Hibernate Validator provide a easily readable yet powerful beginning to a Java program.

Here are the maven dependencies I used to create my example:


<dependency>

<groupId>com.fasterxml.jackson.core</groupId>

<artifactId>jackson-core</artifactId>

<version>2.3.0</version>

</dependency>

<dependency>

<groupId>com.fasterxml.jackson.core</groupId>

<artifactId>jackson-databind</artifactId>

<version>2.3.0</version>

</dependency>

<dependency>

<groupId>com.fasterxml.jackson.dataformat</groupId>

<artifactId>jackson-dataformat-yaml</artifactId>

<version>2.1.3</version>

</dependency>

<dependency>

<groupId>org.hibernate</groupId>

<artifactId>hibernate-validator</artifactId>

<version>5.0.2.Final</version>

</dependency>

<dependency>

<groupId>com.google.guava</groupId>

<artifactId>guava</artifactId>

<version>16.0</version>

</dependency>
<pre>