Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

The State of Bootique, Early 2018 (Part 1)

DZone's Guide to

The State of Bootique, Early 2018 (Part 1)

See what's been going on in the world of Bootique, how the software has changed in the past year, and what the roadmap for the future looks like.

· Java Zone ·
Free Resource

Download Microservices for Java Developers: A hands-on introduction to frameworks and containers. Brought to you in partnership with Red Hat.

It’s been a while since my last Bootique post. Almost a year to be precise. In that time, we had three major releases and lots of new apps deployed on the platform (those that I personally know about are probably in the dozens). So let’s try to fill the gap here and talk about the new features that appeared since then.

Covering such a long period makes the whole story a bit unwieldy, so I am going to split it in two articles (modularity is important in writing, just as much as it is in programming). In the first part we’ll talk about Bootique platform, including new CLI capabilities, better test APIs, etc., and in the second we’ll look at individual module changes. So let’s start…

Java 9

You can run Bootique on Java 9 now. While with the current JDK release cadence, 9 is already obsolete (and so is Java 10!), we need to move forward and stay current.

Coincidentally there is a proposal to start utilizing Java modularity features as a foundation of Bootique module design. Internally Bootique has always been about modules, so there’s lots of synergy. But to get there, we first need to make sure that Bootique is current with the latest JDK.

One Version to Rule Them All

The first thing you may notice after upgrading to either 0.24 or 0.25 is that we no longer let the individual standard modules have their own versions, different from the core. In other words,bootique:0.25 goes together with bootique-jetty:0.25, bootique-jersey:0.25, and so on. This provides quite a bit more sanity to the distribution and makes debugging dependencies much easier.

Startup Errors Reporting: Messages, Not Stack Traces

Often an app can’t start because of a bad or unavailable configuration, invalid CLI flags, and other reasons related to user inputs/environment rather than the app code. In this situation and in accordance to the long-standing Java tradition, past versions of Bootique would print a nasty stack trace and then exit.

This is less than helpful. Whatever useful information is there, it is buried in the middle of the stack trace. Also, the exact line where the app exploded is completely irrelevant when we are not dealing with a bug in the code, but rather with an expected response to an invalid user input.

The new version of Bootique takes a different approach and attempts to classify known exceptions, hide the stack trace, and print only the relevant message (and set a desired process exit code):

$ java -jar myapp.jar -c nosuch.yml

Error running command '-c nosuch.yml': Config resource is not found or is inaccessible: file:/Users/andrus/myapp/nosuch.yml

$ echo $?
1


Your own code can also take advantage of this mechanism. If you want a friendly message for an error condition, you can simply throw BootiqueException with an appropriate message (possibly wrapping another exception). That will tell Bootique to avoid printing stack traces in the terminal.

Now, you’d still occasionally see stack traces where there should be a simple message. Sometimes Guice (our internal DI engine) hides the real causes of errors when resolving DI-managed objects, leaving us with no choice but to dump all available information to the console, letting the user unwind it. We need to address this in the future.

Configuration Improvements

As experience shows, somewhere between 60% and 90% of configuration in a typical app is known upfront and rarely changes. Only a minority of config values are variable in practice. So it almost always makes sense to ship the app with a default embedded configuration and let the users override those few values via various standard mechanisms. This insight led us to a number of improvements in configuration reuse.

Let’s start with how you can actually provide a default config. This is now done by “contributing” a configuration resource (usually the one embedded in the app and referenced via “classpath:” URL) via DI:

BQCoreModule.extend(binder)
    .addConfig("classpath:com/foo/default.yml");


More than one configuration can be contributed, e.g. individual modules might load their own defaults. Multiple contributed configs are combined in a single config tree by the runtime (before being overlaid by various CLI configuration mechanisms and properties). The order in which contributed configs are combined is undefined, so make sure there are no conflicts between them. If there are, consider replacing multiple conflicting configs with a single config.

Default configs contributed per the example above are loaded unconditionally. But you can also make configuration inclusion depend on the presence of a certain command line option:

OptionMetadata o = OptionMetadata.builder("qa")
      .description("when present, uses QA config")
      .build();
BQCoreModule.extend(binder)
      .addOption(o)
      .addConfigOnOption(o.getName(), "classpath:a/b/qa.yml");


In this example, qa.yml config will only be applied if the app is started with the --qa option. In other words, --qa became an alias for -c classpath:a/b/qa.ymlYou can imagine how handy this can be in many cases.

You can also bind a CLI option to just a single property path within the configuration. This can be done straight in the OptionMetadata builder, and you don’t need a .yml embedded in the .jar:

OptionMetadata o = OptionMetadata.builder("db")
      .description("specifies database URL")
      .configPath("jdbc.mydb.url")
      .defaultValue("jdbc:mysql://127.0.0.1:3306/mydb")
      .build();
BQCoreModule.extend(binder).addOption(o);


Another important change to the configuration subsystem is deprecation of the specially named BQ_* shell variables. The original idea to use a naming convention for shell variables to set specific configuration values looked great at the time. E.g. $BQ_JDBC_DB_PASSWORD would be automatically bound to the jdbc.db.password path on the config tree. But during two years of development/deployment of Bootique apps, we came to the realization that this does more harm than good. Especially in non-containerized shared environments, variables defined for certain apps would quickly leak into other apps, causing havoc. Wrong values will be used, or unexpected config nodes created.

So what’s the alternative? Explicit var declaration, of course, that lets the app to namespace its vars and also adds each declared variable to the app help.

BQCoreModule.extend(binder)
    .declareVar("jdbc.db.password", "MYAPP_DB_PASSWORD");


Command Improvements

Previously, commands that started “daemon” processes (like --server or --schedule) would block until the app was killed externally, and were not able to return a proper exit code to the environment. Now such a command would start a daemon on the background and immediately unblock the calling thread:

public class MyCommand implements Command {

    @Override
    public CommandOutcome run(Cli cli) {

        // start some process on background and unblock
        ...
        // return a special outcome
        return CommandOutcome.succeededAndForkedToBackground();
    }
}


This has a number of benefits. First, we can distinguish startup errors from external interrupts. Second, we can actually take over control immediately after the daemon is started (this simplifies/speeds up the test API as described below). Third, since now every command returns predictably, we can make them composable and this brings us to the next feature — command decorators.

Previously, you could only invoke a single command when running an app. This was rather inconvenient, to say the least. E.g. to start a web server that also runs scheduled jobs in the background, you needed to suppress the standard commands and then write a custom command wrapping parallel invocation of Jetty ServerCommand and ScheduleCommand from “bootique-job”. But now you can “decorate” any command with one or more extra commands to be run either before that command or in parallel with it. E.g.:

CommandDecorator extraCommands = CommandDecorator
    .alsoRun(ScheduleCommand.class);  BQCoreModule.extend(binder)
    .decorateCommand(ServerCommand.class, extraCommands);


Now whenever the app is invoked with --server, the scheduler is also started in the background. With parallel alsoRun (and complementary serial beforeRun), we can easily describe custom command invocation trees tied to a single CLI switch, making it a practical API for multiple commands invocation.

Test API Improvements

It took us a lot of experimentation to get the Bootique Test API right. Looks like we are getting close to an ideal API for creating and managing “application objects” in tests that looks and feels like the real app:

@Rule
public BQTestFactory testFactory = new BQTestFactory();

@Test
public void testXyz() {

    BQRuntime runtime = testFactory.app()
       .autoLoadModules()
       // here you can add custom modules, etc.
       .createRuntime();
    // execute the tests asserting the state of the runtime
}


Here runtime is our application object. It's the same as you’d get in a real app. Previously this API returned something called BQTestRuntime, so the actual runtime had to be extracted from the test wrapper. Not anymore. There are no test-specific objects to deal with beyond the factory.

Another example — a Jetty app:

@Rule
public BQTestFactory testFactory = new BQTestFactory();

@Test
public void testXyz() {
    // start the Jetty server
    testFactory.app("--server")
       .autoLoadModules()
       // here you can add custom modules, etc.
       .run();
    // execute the tests
}


It looks pretty much the same as the example above. Same factory, same flow (.run() is a shorthand for .createRuntime().run()). In the past, we’d have to use a special factory (presently deprecated) — JettyTestFactory. But now it no longer matters whether the app is a daemon or a command that does its work and exits. In both cases, runtime assembly can be handled by a single BQTestFactory. The fact that we want to start a daemon is discerned automatically from the CLI command (.app("--server")).

The new approach also turned out to be noticeably faster than the previous one. Thanks to background commands, we no longer need to poll the server to check whether it finished its startup sequence before running the tests. Whenever the --server command goes on background, we know that the app object is ready to serve requests.

Download Building Reactive Microservices in Java: Asynchronous and Event-Based Application Design. Brought to you in partnership with Red Hat

Topics:
java ,bootique ,java 9

Published at DZone with permission of

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}