Ollie is Java-based application framework designed to simplify the creation and distribution of REST-API enabled server applications.
The creators of Ollie saw that many apps had common structures. They would have some kind of dependency injection framework, persistence, api documentation, a way to build and run the app, etc.. The goal with Ollie is to abstract as much of this common architecture as possible so that you, the user, can focus your attention on developing your application. By using Ollie you automatically get Google Guice, Rest Api, swagger, configuration files, and packaging via provisio (more on these below).
Add the takari parent as your parent POM:
<parent>
<groupId>io.takari</groupId>
<artifactId>takari</artifactId>
<version>27</version>
</parent>
Add the ollie-targetplatform to your dependencyManagement in your POM.xml:
<dependencyManagement>
<dependencies>
<dependency>
<groupId>com.walmartlabs.ollie</groupId>
<artifactId>ollie-targetplatform</artifactId>
<version>${ollie.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
Note that this was written for Ollie version 0.0.20. You can find the latest version here: http://repo1.maven.org/maven2/com/walmartlabs/ollie/ollie/
Add ollie to your list of dependencies in your POM.xml:
<dependency>
<groupId>com.walmartlabs.ollie</groupId>
<artifactId>ollie</artifactId>
</dependency>
The guice framework comes built in when using Ollie. I won’t go into details here on how to use Guice but I will show how Ollie interacts with it.
By using Ollie you can use an OllieServerBuilder to create a Guice server by specifying the port, name, package of your project to be scanned for the dependency injector, and the file where secret configuration values can be found (more on this in the configuration files section).
Here is an example using OllieServerBuilder to set up and start a server:
OllieServerBuilder builder = new OllieServerBuilder()
.port(9000)
.name("gatekeeper")
.packageToScan("com.walmartlabs.gatekeeper")
.secrets(new File("/secrets/secrets.properties"));
server = builder.build();
server.start();
Now any class in the packageToScan can be used for Guice dependency injection or it can be used as an API listener with Swagger documentation.
To set up an API listener with Swagger documentation you simple make a class
that implements org.sonatype.siesta.Resource
(you may need to add this to
your POM in order to use it) and uses the annotations in the example below
(Swagger and Guice documentation can show you other annotations that can be
used):
@Named
@Singleton
@Path("/ping")
@Api(value = "/ping", tags = "ping")
public class PingListener implements Resource {
@GET
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
@ApiOperation(value = "ping")
public Response processPing() {
return Response.ok().build();
}
}
Once you start your server you can see the Swagger documentation by
navigating to <hostname>:<port>/docs
. So if you are running your app
locally on port 9000 you would go to localhost:9000/docs
Ollie allows you to add a configuration file to your project in the
src/main/resources
folder called <projectName>.conf
. You will
automatically be able to use values in the *.conf file by using the @Config
annotation and you can set up configurations for multiple environments in
the same configuration file.
Let’s look at my gatekeeper.conf file as an example:
gatekeeper {
// Shared application configuration regardless of environment
development {
jira.server = "https://jira-dev.walmart.com"
jira.username = ${jira.username}
jira.password = ${jira.password}
crq.gate.enabled = true
}
production {
jira.server = "https://jira-prod.walmart.com"
jira.username = ${jira.username}
jira.password = ${jira.password}
crq.gate.enabled = false
}
}
As we can see, in the gatekeeper project (note that this name must match the
name provided when starting your OllieServer) there are two environments with
the same keys but different values: development and production. Ollie will
choose which environment to use based on a parameter passed in at runtime
called -Dollie.environment
. We will see how this is passed in later, but
when run locally this will default to development.
Configuration values can be accessed by using the @Config
annotation:
@Inject
public DefaultCrqJiraClient(@Config("jira.server") String server, @Config
("jira.username") String username,
@Config("jira.password") String password) {
this.server = server;
this.username = username;
this.password = password;
}
The config annotation can be used to inject configuration values into any variable but it is recommended to use it for constructor injection as shown above.
In the *.conf
file, if you use the syntax ${example.name}
, Ollie will
automatically go look in the file you configured when you set up the server
for the value. In our example it will go look in /secrets/secrets .properties
. I use the /secrets/secrets.properties
location because I use
a tool called "keywhiz" to distribute sensitive information out to the
compute(s) my app runs on and keywhiz automatically places them in the
/secrets
directory. You can configure your server to look for secret data
wherever you like. Ideally you would use a tool like keywhiz to distribute
your secret values to your computes in a known location. Ollie will then
access these secret values by using the ${}
syntax in the *.conf
file.
This way, your conf file can be pushed to github without revealing secure
information. For completion purposes, here is what the secrets.properties
file would look like for our example:
jira.username=myUsername
jira.password=superSecure
Ollie allows you to use a packaging method called provisio by using the
<packaging>
tag in your POM.xml
<groupId>com.walmartlabs.gatekeeper</groupId>
<artifactId>gatekeeper</artifactId>
<version>0.0.9-SNAPSHOT</version>
<packaging>provisio</packaging>
Using the provisio packaging will ultimately create a
<projectNameAndVersion>.tar.gz
file when you build your project. If you
were to unpack this tar.gz file you would find a bin
, etc
, and lib
folder. The lib
folder holds your projects JAR as well as all of your
dependency JARs. The etc
folder contains some configuration files used to
define your main class among other things. The bin
folder contains a
launcher python script to run your main class by executing ./bin/launcher start
(or optionally ./bin/launcher start -Dollie.environment=production
to override the default environment used by the configuration).
Ultimately provisio creates a tar.gz which is an easily exportable and runnable artifact with all the details it needs to run your application.
To allow provisio packaging to work properly you need to add an etc
folder
and a provisio
folder to your project’s src/main
folder.
The etc
folder will have two files:
config.properties
which will contain your server port information:
http-server.http.port=8000
jvm.config
which will contain:
-server
-Xmx1G
-XX:+UseG1GC
-XX:G1HeapRegionSize=32M
-XX:+UseGCOverheadLimit
-XX:+ExplicitGCInvokesConcurrent
-XX:+HeapDumpOnOutOfMemoryError
-XX:+ExitOnOutOfMemoryError
-Dollie.environment=production
This will set up some defaults for the launcher script to use. You will notice the last line sets up the environment to be used by the configuration file that we discussed before. You are able to add other configuration files for things like loggers. The launcher itself serves as a sort of readme if you open it in a text editor for more details.
The provisio
folder will have one file:
server.xml
which contains info on how to construct the tar.gz:
<runtime>
<archive name="${project.artifactId}-${project.version}.tar.gz"/>
<!-- Notices -->
<fileSet to="/">
<directory path="${basedir}">
<include>NOTICE</include>
<include>README.txt</include>
</directory>
</fileSet>
<!-- Launcher -->
<artifactSet to="bin">
<artifact id="io.airlift:launcher:tar.gz:bin:${launcherVersion}">
<unpack/>
</artifact>
<artifact id="io.airlift:launcher:tar.gz:properties:${launcherVersion}">
<unpack filter="true"/>
</artifact>
</artifactSet>
<fileSet to="/etc">
<directory path="${basedir}/src/main/etc"/>
</fileSet>
<!-- Server -->
<artifactSet to="/lib" ref="runtime.classpath"/>
</runtime>
This relies on a property which will need to be specified
in your pom if it isn't already. At the time this was written I was using
<launcherVersion>0.124</launcherVersion>
Ollie can optionally allow for easy configuration of a database layer using Liquibase and Jooq. Please see the db-example readme for implementation details.
Ultimately the goal is for a build agent to build your application, place the
created tar.gz artifact in some kind of repository manager, and then kick off
your deployment job. Your deployment job will provision your compute
information, place your tar.gz artifact on that compute(s), and then unpack
the tar.gz and execute the launcher script with ./bin/launcher start -Dollie .environment=production
.