Creating Microservices with Bootique

September 18th, 2016 by

When it comes to writing microservices in Java, plenty of tools and frameworks exist. In the following tutorial, I’d like to demonstrate another minimalistic framework called Bootique by implementing a simple microservice exposing its functions either as a RESTful web-service or as a runnable command executed using the command line.

Bootique Command Line

Bootique Command Line

 

Dependencies

Using Maven here, we’re adding the following elements to our project’s pom.xml:

  • Bootiques Bill of Materials as dependency management: bootique-bom
  • Bootique Jersey for our REST service: bootique-jersey
  • Bootique Logback for logging: bootique-logback
  • Maven Shade Plugin to assemble our fat-jar

I have documented the complete pom.xml in the appendix here.

Bootique Microservice

Out wonderful microservice allows us to obtain the current date either using a RESTful web-service or as a command in the command line.

It’s using Guice for dependency injection and wiring of our components.

Date Service

This is our date service that prints the current date using a default format or using a specific date format to print the date.

package com.hascode.tutorial;
 
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
 
public class DateService {
    public String getDate() {
        return getDate("yyyy-MM-dd");
    }
 
    public String getDate(String dateFormat) {
        ZonedDateTime dt = ZonedDateTime.now();
        return String.format("In %s it's %s", dt.getZone(), dt.format(DateTimeFormatter.ofPattern(dateFormat)));
    }
}

If you have never heard of the new Date/Time APIs in Java 8, please feel free to take a look at the following presentation of mine: “The Time is now! Date/Time APIs in Java 8“.

RESTful Webservice

Our JAX-RS webservice uses the date service to print the current date as plain text.

package com.hascode.tutorial;
 
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
 
import com.google.inject.Inject;
 
@Path("/")
public class DateWebService {
    @Inject
    private DateService dateService;
 
    @GET
    @Produces(MediaType.TEXT_PLAIN)
    public Response getCurrentDate() {
        return Response.ok(dateService.getDate()).build();
    }
}

Application

This is our application class that is responsible for wiring and configuring our application using Guice.

package com.hascode.tutorial;
 
import com.google.inject.Inject;
import com.google.inject.Module;
import com.google.inject.Provides;
import com.google.inject.Singleton;
import com.google.inject.name.Named;
 
import io.bootique.BQCoreModule;
import io.bootique.Bootique;
import io.bootique.jersey.JerseyModule;
 
public class Application {
    public static void main(String[] args) {
        Module module = binder -> {
            JerseyModule.contributeResources(binder).addBinding().to(DateWebService.class);
            BQCoreModule.contributeCommands(binder).addBinding().to(DateOutputCommand.class);
        };
        Bootique.app(args).module(module).autoLoadModules().run();
    }
 
    @Provides
    @Singleton
    public DateService provideDateService() {
        return new DateService();
    }
}

Bootique Command

This is our command that allows us to specify the date format used for printing the current date via command line.

In addition we’re using the CommandMetadata builder to add some information to this additional command.

package com.hascode.tutorial;
 
import com.google.inject.Inject;
 
import io.bootique.cli.Cli;
import io.bootique.cli.CliOption;
import io.bootique.command.CommandMetadata;
import io.bootique.command.CommandOutcome;
import io.bootique.command.CommandWithMetadata;
 
public class DateOutputCommand extends CommandWithMetadata {
    private static final String PARAM_DATEFORMAT = "dateFormat";
 
    public DateOutputCommand() {
        super(CommandMetadata.builder(DateOutputCommand.class)
                .addOption(CliOption.builder(PARAM_DATEFORMAT, "The date format used to format the current date")
                        .valueOptional().build())
                .description("Print out the current date (specify format with --dateFormat)"));
    }
 
    @Inject
    private DateService dateService;
 
    @Override
    public CommandOutcome run(Cli cli) {
        try {
            String dateFormat = cli.optionString(PARAM_DATEFORMAT);
            if (dateFormat == null) {
                System.out.println(dateService.getDate());
                return CommandOutcome.succeeded();
            }
 
            System.out.println(dateService.getDate(dateFormat));
            return CommandOutcome.succeeded();
        } catch (IllegalArgumentException e) {
            return CommandOutcome.failed(1, e);
        }
    }
 
}

Building the Microservice

We may now build our microservice as a fat jar using Maven:

$ mvn clean package                                                                                                                                                                                                                                                    130[..]
[INFO] ------------------------------------------------------------------------
[INFO] Building bootique-microservice-tutorial 1.0.0
[INFO] ------------------------------------------------------------------------
[..]
[INFO] --- maven-shade-plugin:2.4.2:shade (default) @ bootique-microservice-tutorial ---
[INFO] Including io.bootique.jersey:bootique-jersey:jar:0.18 in the shaded jar.
[INFO] Including io.bootique:bootique:jar:0.19 in the shaded jar.
[..]
[INFO] Replacing original artifact with shaded artifact.
[INFO] Replacing /data/project/bootique-microservices-tutorial/target/bootique-microservice-tutorial-1.0.0.jar with /data/project/bootique-microservices-tutorial/target/bootique-microservice-tutorial-1.0.0-shaded.jar
[INFO] Dependency-reduced POM written at: /data/project/bootique-microservices-tutorial/dependency-reduced-pom.xml
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 2.909 s
[INFO] Finished at: 2016-09-18T17:20:23+01:00
[INFO] Final Memory: 37M/264M
[INFO] ------------------------------------------------------------------------

Running the Microservice

When running the jar, our application prints available options and commands to the command line:

$ java -jar target/bootique-microservice-tutorial-1.0.0.jar
Option                    Description
------                    -----------
--config <yaml_location>  Specifies YAML config location, which
                            can be a file path or a URL.
--dateFormat              The date format used to format the
                            current date
--dateoutput              Print out the current date (specify
                            format with --dateFormat)
--help                    Prints this message.
--server                  Starts Jetty server.

To start our RESTful webservice we now only need to add the parameter –server to the command:

$ java -jar target/bootique-microservice-tutorial-1.0.0.jar --server
INFO  [2016-09-18 15:36:26,611] main o.e.jetty.util.log: Logging initialized @691ms
INFO  [2016-09-18 15:36:26,644] main i.b.j.s.ServerFactory: Adding listener io.bootique.jetty.servlet.DefaultServletEnvironment
INFO  [2016-09-18 15:36:26,647] main i.b.j.s.ServletFactory: Adding servlet 'jersey' mapped to /*
INFO  [2016-09-18 15:36:26,659] main i.b.j.s.ServerLifecycleLogger: Starting jetty...
INFO  [2016-09-18 15:36:26,661] main o.e.j.server.Server: jetty-9.3.z-SNAPSHOT
INFO  [2016-09-18 15:36:26,985] main o.e.j.s.h.ContextHandler: Started o.e.j.s.ServletContextHandler@78461bc4{/,null,AVAILABLE}
INFO  [2016-09-18 15:36:26,995] main o.e.j.s.ServerConnector: Started ServerConnector@3546d80f{HTTP/1.1,[http/1.1]}{0.0.0.0:8080}
INFO  [2016-09-18 15:36:26,995] main o.e.j.server.Server: Started @1077ms
INFO  [2016-09-18 15:36:26,995] main i.b.j.s.ServerLifecycleLogger: Started Jetty in 336 ms. Base URL: http://127.0.0.1:8080/

Using Configuration File

We may also specify an external configuration file to use .. e.g. this configuration file in YAML format here:

dateservice:
  dateFormat: 'hh:mm:ss yyyy-MM-dd'
jetty:
  context: /hascode
  connector:
    port: 3333

The configuration file is specified using the –config parameter here:

$ java -jar target/bootique-microservice-tutorial-1.0.0.jar --server --config config.yml                                                                                                                                                                               130 ↵
Reading configuration at config.yml
INFO  [2016-09-18 15:37:17,511] main o.e.jetty.util.log: Logging initialized @739ms
INFO  [2016-09-18 15:37:17,544] main i.b.j.s.ServerFactory: Adding listener io.bootique.jetty.servlet.DefaultServletEnvironment
INFO  [2016-09-18 15:37:17,547] main i.b.j.s.ServletFactory: Adding servlet 'jersey' mapped to /*
INFO  [2016-09-18 15:37:17,559] main i.b.j.s.ServerLifecycleLogger: Starting jetty...
INFO  [2016-09-18 15:37:17,561] main o.e.j.server.Server: jetty-9.3.z-SNAPSHOT
INFO  [2016-09-18 15:37:17,897] main o.e.j.s.h.ContextHandler: Started o.e.j.s.ServletContextHandler@51768776{/hascode,null,AVAILABLE}
INFO  [2016-09-18 15:37:17,936] main o.e.j.s.ServerConnector: Started ServerConnector@4fbdc0f0{HTTP/1.1,[http/1.1]}{0.0.0.0:3333}
INFO  [2016-09-18 15:37:17,936] main o.e.j.server.Server: Started @1167ms
INFO  [2016-09-18 15:37:17,937] main i.b.j.s.ServerLifecycleLogger: Started Jetty in 377 ms. Base URL: http://127.0.0.1:3333/hascode

As we can see, our application is now running on port 3333 instead of 8080.

Accessing the Webservice

Running the –server command starts our service so that it should be accessible with a simple GET request to localhost:8080/3333

Accessing the Bootique REST Service in the Browser

Accessing the Bootique REST Service in the Browser

Running a Command

We may also obtain the current date using our Bootique command and we may specify the dateformat used by adding the parameter dateFormat:

$ java -jar target/bootique-microservice-tutorial-1.0.0.jar --dateoutput
In Europe/Berlin it's 2016-09-18
 
$ java -jar target/bootique-microservice-tutorial-1.0.0.jar --dateoutput --dateFormat "dd MMM yy hh:mm"
In Europe/Berlin it's 18 Sep 16 05:39

Resources

Tutorial Sources

Please feel free to download the tutorial sources from my Bitbucket repository, fork it there or clone it using Git:

git clone https://bitbucket.org/hascode/bootique-microservice-tutorial.git

Appendix A: Maven Configuration

This is the complete pom.xml used for this application.

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<groupId>com.hascode.tutorial</groupId>
	<artifactId>bootique-microservice-tutorial</artifactId>
	<version>1.0.0</version>
 
	<parent>
		<groupId>io.bootique.parent</groupId>
		<artifactId>bootique-parent</artifactId>
		<version>0.12</version>
	</parent>
 
	<properties>
		<main.class>com.hascode.tutorial.Application</main.class>
	</properties>
 
	<dependencyManagement>
		<dependencies>
			<dependency>
				<groupId>io.bootique.bom</groupId>
				<artifactId>bootique-bom</artifactId>
				<version>0.19</version>
				<type>pom</type>
				<scope>import</scope>
			</dependency>
		</dependencies>
	</dependencyManagement>
 
	<dependencies>
		<dependency>
			<groupId>io.bootique.jersey</groupId>
			<artifactId>bootique-jersey</artifactId>
			<scope>compile</scope>
		</dependency>
		<dependency>
			<groupId>io.bootique.logback</groupId>
			<artifactId>bootique-logback</artifactId>
			<scope>compile</scope>
		</dependency>
	</dependencies>
	<build>
		<plugins>
			<plugin>
				<groupId>org.apache.maven.plugins</groupId>
				<artifactId>maven-shade-plugin</artifactId>
			</plugin>
			<plugin>
				<groupId>org.apache.maven.plugins</groupId>
				<artifactId>maven-compiler-plugin</artifactId>
				<version>3.3</version>
				<configuration>
					<source>1.8</source>
					<target>1.8</target>
				</configuration>
			</plugin>
		</plugins>
	</build>
</project>
Search
Categories