Skip to content

Demo of a containerized Raml & JaxRS based service project.

License

Notifications You must be signed in to change notification settings

VEuPathDB/example-jaxrs-container-service

Repository files navigation

Example Containerized JaxRS Service

ℹ️
This repository is a thin demonstrative wrapper around the Core container library, for more in depth documentation of available features and config options see the core library’s readme.

Running the Example

Locally

Prerequisites

Setup & Run

Environment/dependency setup for local development can be done via the command make install-dev-env. This will install the dependencies required to build the project.

After the environment has been set up, the project can be built, tested, and packaged using make jar.

Starting the server can be done by running java -jar build/libs/service.jar.

⚠️

Most projects will require some CLI or environment arguments to run. For a full listing of available options see the core container library documentation.

ℹ️

All project specific system level dependencies must be installed locally for this to work.

In Docker

Prerequisites

  • Docker

  • Docker Compose

Setup & Run

The project container can be built using make docker. The first build of the container will take several minutes.

Running the built container can be done with:

$ docker-compose -f docker/docker-compose.dev.yml up
⚠️

Most projects will require some CLI or environment arguments to run. For a full listing of available options see the core container library documentation. When using docker-compose these options can be set automatically through the use of a .env file.

Development

Quick Start for New Projects

  1. Create a new project from this template by clicking the "Use this template" button above.

  2. Clone your new project and cd into the clone directory

  3. Run make new-project-initialization
    This will:

    1. Link your new project’s git history with the git history of this template repo which will enable the ability to pull fixes and updates from this template into your new project.

    2. Install the dev tools needed to build and test your project.

  4. Edit build.gradle.kts with your service’s details

  5. Edit api.raml with your API design

  6. Create any necessary types in docs/schema

  7. Run make gen-jaxrs to generate skeleton code

  8. Further rename and edit the Java packages and source code under src/ as desired

  9. Implement the generated service interfaces

  10. Register your services in the Resources class

  11. Run make test to run any unit tests and verify that things compile

Quick Start for Contributing

  1. Clone this project and cd into the clone directory

  2. Run make link-template-repo
    This will link your currently checked out project back up to the template repo which will enable the ability to pull in fixes and updates published to that template.

  3. Run make install-dev-env
    This will install the dev tools needed to build and test the project.

  4. Edit build.gradle.kts with your service’s details

  5. Edit api.raml with your API design

  6. Create any necessary types in docs/schema

  7. Run make gen-jaxrs to generate skeleton code

  8. Further rename and edit the Java packages and source code under src/ as desired

  9. Implement the generated service interfaces

  10. Register your services in the Resources class

  11. Run make test to run any unit tests and verify that things compile

Detailed Overview

Environment Prerequisites

NPM

Required to run raml2html

Maven

Required to build raml-to-jaxrs

Configuration

Configuration for the build is primarily done through the build.gradle.kts file. Here you can configure the project’s name, version, container name, Java package structure, etc. as well as define your project’s dependencies.

For information about adding gradle dependency declarations see the Gradle docs.

API definition

The service api is defined in 2 places initially:

  1. The api.raml file which contains the server endpoints.

  2. The schema/url folder which contains type schema definitions.
    These files must be valid Raml 1.0 Libraries.[1][2]

api.raml
Including Type Definitions

In the api.raml file, there is a statement near the top of the file that declares the Raml file "uses" schema/library.raml. This is a generated file based on the contents of the schema library under schema. The library.raml file should not be edited directly.

api.raml: uses declaration
uses:
  err: .tools/raml/errors.raml
  lib: schema/library.raml

The uses keyword maps an import alias to the imported library. This import alias is used to access the types defined in that library. In the case of the above example, library types would be available using lib.{MyTypeName}

api.raml: Library type usage.
        body:
          application/json:
            type: lib.HelloResponse
Type Schema

Each raml library file under schema should define a root types object defining the types used by the API. The name of the types defined under the types object will be the name of the generated Java classes based on those types.

Example 1. Type definition

 

Schema
#%RAML 1.0 Library
types:
  MyType:
    properties:
      foo: string
Resulting Java Interface
package org.veupathdb.service.demo.generated.model;

import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;

@JsonDeserialize(
    as = HealthResponseImpl.class
)
public interface MyType {
  @JsonProperty("foo")
  String getFoo();

  @JsonProperty("foo")
  void setFoo(String foo);
}
Resulting Java Class
package org.veupathdb.service.demo.generated.model;

import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonPropertyOrder;

@JsonInclude(JsonInclude.Include.NON_NULL)
@JsonPropertyOrder({
    "foo",
})
public class HealthResponseImpl implements HealthResponse {
  @JsonProperty("foo")
  private String foo;

  @JsonProperty("foo")
  public String getFoo() {
    return this.foo;
  }

  @JsonProperty("foo")
  public void setFoo(String foo) {
    this.foo = foo;
  }
}

Generating a Service

Once your API spec is complete, you can begin development of Java code by running make gen-jaxrs. This will create a skeleton of the API in the generated source package located under the root package defined using the projectPackage value in build.gradle.kts.

The generated interfaces and types have the basic necessary annotations for use by both Jackson and Jersey.

Once you have implemented the interfaces defined under {source-package}.generated.resources they must be registered in the {source-package}.Resources class.

Run Your Service

Running your service locally can be done by following the same steps as defined above in the Running the Example section:

  1. Run make jar

  2. Run java -jar build/libs/service.jar

Running in Docker can be done by:

  1. Run make build-docker

  2. Run docker run <your-image-name>

Other Information

Authentication

The base service contains an authentication layer that will be enabled on any service class or method annotated with @Authenticated. This authentication will validate a VEuPathDB user session against the configured account- (for registered users) and user- (for guest users) databases and append user profile information to the incoming request object.

Make Targets

Meta Targets

help
  1. Lists the available make targets.
    This is the default target that will be executed if you just run make.

Project & Environment Setup Targets

new-project-initialization
  1. Runs the make target link-template-repo

  2. Merges the git history of a new template-based project with the git history of template that the project was based on.

  3. Runs the make target install-dev-env

link-template-repo
  1. Links the currently cloned repository back up to the template repository from which the project was created.
    This is done to allow pulling in fixes and updates from the template into the template based project.

merge-template-repo
  1. Pulls in changes from the template repo from which this project was created.

install-dev-env
  1. Checks for system prerequisites.

  2. Downloads and builds raml-to-jaxrs.

  3. Installs the Oracle JDBC components into the vendor directory.

  4. Downloads, builds, and installs the FgpUtil project into the vendor directory.

  5. Installs the required NPM packages

Build & Test Targets

compile
  1. Generates code & docs if the API def has changed.

  2. Compiles Java code if anything has changed.

test
  1. Generates code & docs if the API def has changed.

  2. Compiles Java code if necessary.

  3. Runs unit tests.

jar
  1. Generates code & docs if the API def has changed.

  2. Compiles Java code if necessary.

  3. Runs unit tests if necessary.

  4. Packages a self-contained runnable jar.

docker
  1. Runs docker build for the project.

Code & Doc Generation

raml-gen-code
  1. Generates JAX-RS Java code from the RAML spec.

raml-gen-docs
  1. Generates API docs from the RAML spec.

Build Process

Presently the build process is operated through a makefile which calls and sets up the necessary prerequisites. This is a temporary solution to be used until all the utilities in the build utils project are migrated to the gradle plugin project.

Generating Jax-RS Code

The make gen-jaxrs command is backed by the Mulesoft Raml-for-JaxRs library.

Hiccups and limitations

The RAML to JaxRS conversion library has the following known issues that are likely to impact use of the tool:

Enums
  • Enum generation creates types that do not allow access to the raw backing text, which may be desired when constructing complex responses.

  • Enum generation may cause the generator to fail with cryptic errors involving bad imports for the Java builtin type String

One alternative/workaround for this is defining the enum type in Java and typing the RAML as string with the possible values defined as examples.

Inheritance

Extending types can work out for trees with at most 1 parent depth, however going beyond that may cause things to generate in a way that has compile errors.

additionalProperties aka //:

Using a catchall block for maps with anything other than a simple value type will cause the java type to be Map<String, Object>. Additionally even a simple value type may have this result.

Tunnels and Proxies

When developing or testing a service, you may need to provide that service access to resources that cannot be reached without a tunnel or proxy. This is complicated by the fact that Docker containers, by default, will not see those resources.

To allow Dockerized services access to these external resources, the running container(s) must be given access to the host machine’s network. This can be done via docker run using the network mode "host" or via docker-compose by setting the network_mode config option to "host".

Binding the containers to your host machine’s network will give the containers access to your running tunnels/proxies, but will also mean binding all exposed container ports to those ports on your host machine which may cause conflicts.

The project root

The root directory of this project contains multiple standard files used by Gradle, Docker, Make, and GitHub, in addition to custom files created for developer convenience.

Table 1. Root Directory Files
File For Description

.dockerignore

Docker

Similar to .gitignore files, tells Docker to ignore specified file patterns when performing glob copies from the source directory into the container being built.

.gitignore

Git

Tells git to ignore files matching the specified patterns.

api.raml

RAML

API endpoint definition.

build.gradle.kts

Gradle

Standard Gradle build script definition.[3] This particular build script uses the Kotlin DSL for gradle. The project configuration is set here and passed to the gradle plugin, and all other project dependencies are also set here.

Dockerfile

Docker

Docker container definition file.[4]

gradlew

Gradle

Standard, autogenerated Gradle script that allows the use of Gradle without requiring the host machine itself to have Gradle installed.

makefile

Make

Convenience wrapper for the build utils until the required tasks are performed which will allow simplifying the build. See Make Targets for usage.

readme.adoc

GitHub

Asciidoc readme file (the source backing this page).

settings.gradle.kts

Gradle

File read by gradle before build.gradle.kts; provides the repository and credentials to download the VEuPathDB gradle plugin. This configuration cannot be contained in build.gradle.kts itself.

Table 2. Root Directory Dirs
Directory For Description

docker/

Docker/Docker Compose

Contains docker-compose configuration files.

gradle/

Gradle

Contains the backing source for the gradlew script in the root directory.
Keeping this directory in your repository is standard practice for Gradle.

schema/

RAML

Contains RAML type definitions imported by the api.raml file in the root directory. The schema/library.raml file is autogenerated based on the other files present in the schema directory.

src/

Java source root