/ Groovy Programming


Building RESTful Web Services with Groovy

There are many techniques and frameworks for making micro-services on the JVM today. Some of them are well known, other less so. In this article I'm going to go through how you can leverage Groovy's expressiveness combined with the robust micro-service framework Ratpack to create services that consumes less resources and contains less boilerplate and annotations.

Why Groovy?

It is now roughly 6 years since I started exploring what Groovy can do. Since then Groovy has taken huge steps forward along with the JVM platform. Before there was Java 8, Kotlin, Scala and numerous other languages Groovy already had invented most functional paradigms the "new kids on the block" are so touting today. While it was a bit slow still on Java 6, the introduction of invoke-dynamic in the JVM made it super-fast, almost as fast as Java itself making it a #1 language to use on the JVM.

I think Groovy's expressiveness combined with the low gradual learning curve if you are coming from Java makes it a perfect language to use both for configuration (Gradle, Jenkins) as well as application frameworks (Grails, Ratpack).

Many programmers today are also getting tired of the annotation hell Java applications are becoming (for example with Spring Framework) where functionality is hidden behind a black box annotation. Groovy's DSL support provides a nice simple alternative to annotations.

Ratpack 101 (and hopefully not 404)

Before we can begin, I need to introduce the star of the show, the Ratpack framework.

I have now been using Ratpack for some time and it has started to grow more and more on me, turning out to be a great toolbox for writing Groovy based services. Its strength is its easy learning path to both Groovy, Groovy's DSLs and building end-points (EPs). Another strength of it is that it will be very lightweight since it will be running on the lightweight Netty server which is crucial if you are spinning up lots of services.

But, instead of you believing me , let me show you how to make an app so you can judge for yourself.

Since I like to keep examples relevant I will not write a "Hello world!" type of application, you will easily find a such example with Google anyhow. Instead, I will show you how to make an EP that does currency conversions using the European Currency Exchange's (ECE) daily currency rate and caches them in a local database to reduce the hits on ECE. I will show you how you can set up a in-memory H2 database as well as initialize it using Flyway scripts. Finally I will show you how to package and deploy it with Gradle to Docker.

Sounds hard? I assure you if you can write a hello world app you can write what I just described in no time with Ratpack and Groovy.

So lets get crackin'!

We will start by creating our EP with Ratpack. So I create a new file called Ratpack.groovy and add the following logic to it:

import groovy.sql.Sql
import org.flywaydb.core.Flyway
import org.javamoney.moneta.FastMoney
import ratpack.h2.H2Module
import ratpack.service.Service
import ratpack.service.StartEvent

import javax.sql.DataSource

import static javax.money.convert.MonetaryConversions.getConversion
import static ratpack.groovy.Groovy.ratpack
import static ratpack.jackson.Jackson.json

ratpack {

    bindings {

        // Use H2 in-memory database
        module(new H2Module("sa", "", "jdbc:h2:mem:conversions;DB_CLOSE_DELAY=-1"))

        // Migrate flyway scripts on startup
        bindInstance new Service() {
            void onStart(StartEvent event) {
                new Flyway(dataSource: event.registry.get(DataSource)).migrate()
            }
        }
    }

    handlers {

        /*
            GET /convert/EUR/USD/100
         */
        get("convert/:from/:to/:amount") {

            // 1: Try to find the conversion from the db
            def amount = getAmountFromDb(get(DataSource), pathTokens.from, pathTokens.to, pathTokens.amount as BigDecimal)

            if(!amount) {
                // 2: Do the conversion via ECE
                amount = FastMoney.of(pathTokens.amount.toBigDecimal(), pathTokens.from)
                        .with(getConversion(pathTokens.to)) .number.toBigDecimal()

                // 3: Store the conversion for future reference
                setAmountToDb(get(DataSource), pathTokens.from, pathTokens.to, pathTokens.amount as BigDecimal, amount)
            }

            // 4: Render a response for a our clients
            render json([
                    "fromCurrency" : pathTokens.from,
                    "toCurrency" : pathTokens.to,
                    "fromAmount": pathTokens.amount.toBigDecimal().stripTrailingZeros(),
                    "toAmount": amount.toBigDecimal().stripTrailingZeros()
            ])
        }
    }
}

/*
 * Helper function to retrive an amount from the db
 */ 
static Number getAmountFromDb(DataSource ds, String from, String to, Number amount) {
    new Sql(ds).firstRow("""
        SELECT toAmount FROM conversions 
        WHERE fromCurrency=:from
        AND toCurrency=:to
        AND fromAmount=:amount
    """, ['from': from, 'to': to, 'amount': amount])?.values()?.first()
}

/*
 * Helper function to set an amount in the db
 */ 
static void setAmountToDb(DataSource ds, String from, String to, Number amount, Number convertedAmount) {
    new Sql(ds).executeInsert("""
        INSERT INTO conversions (fromCurrency, toCurrency, fromAmount, toAmount) 
        VALUES (:from, :to, :amount, :toAmount) 
    """, ['from': from, 'to': to, 'amount': amount, 'toAmount': convertedAmount])
}

That is the whole application, nothing more to it.

When we run the application and call /convert/EUR/USD/100 we will recieve the following json response:

{
  "fromCurrency": "EUR",
  "toCurrency": "USD",
  "fromAmount": 100,
  "toAmount": 116.99
}

But lets go through the code in more detail.

We start with the ratpack{}-closure. This is the main closure for any ratpack application. All logic to configure the ratpack app goes here.

Next, lets have a look at the bindings{}-closure.

Bindings in Ratpack are the equivalent of dependency injection in other frameworks. What you do is bind an interface (or concrete class) to an implementation of that class and then in your handlers you can get the implementation by only using the interface. In Ratpack this can be taken further to have multiple hierarchical registries with multiple implementations, but that is a more advanced topic for later.

In our simple application we register two implementations; the H2Module and a anonymous service.

A module in Ratpack is a way to bundle functionality into an easily consumable form by Ratpack applications. Most libraries you see will have a module for you to bind into the project, in our case we are using the H2 module that will allow us to use an in-memory database. What the module will do in the background is register a Datasource class with the registry, which we then can use in our handlers to do queries to the database with.

We also bind a service to allow us to integrate a non-ratpack dependency into the service life-cycle. In our case we use Flyway to migrate our database tables on application start to our in-memory database defined above. Since it does not have a Ratpack module for us, we just wrap it in a service to do our bidding.

If we were good open source developers we could write a Ratpack module for flyway with exactly that service and release it to the public and gain fame and fortune ;)

Alright, now that we have our database set up, lets look at our single EP.

We define our EPs in the handlers{}-closure. In our case we will have one EP for the currency conversion so we bind it to GET /convert/:from/:to/:amount. What this syntax means is that when our endpoint is invoked with the URI /convert/EUR/SEK/100 it will return the conversion rate for 100 EUR into SEK.

The implementation of the EP is rather trivial; we first check if we already have a conversion for that amount in the database, if it exists we just use it, otherwise we use the Monetery API (JSR354) to retrieve the conversion via ECE and then we just store it in the database for future use.

Once we have the conversation rate we just construct a JSON object to return for the client.

And that is it, application done, time to package it up and run it.

Note: The above example is written solely using the Ratpack DSL which is convenient for small EPs like this. For bigger projects you will want to split out the handlers into their own classes and only define the routes and bindings in the Ratpack.groovy file. I have made this same example using that approach at https://github.com/devsoap/examples/tree/master/currency-converter-ratpack-ext.

Packaging Ratpack applications with Gradle and Docker

Alright, time to wrap up and deploy our cool new service.

Here is the Gradle file to do it:

buildscript {
    repositories {
        jcenter()
    }
    dependencies {
        classpath "io.ratpack:ratpack-gradle:1.5.4"
        classpath 'se.transmode.gradle:gradle-docker:1.2'
    }
}

apply plugin: "io.ratpack.ratpack-groovy"
apply plugin: 'groovy'
apply plugin: 'docker'

repositories {
    jcenter()
}

dependencies {
    compile 'javax.money:money-api:1.0'
    compile 'org.javamoney:moneta:1.0'
    compile 'org.slf4j:slf4j-simple:1.7.25'
    compile 'org.flywaydb:flyway-core:4.0.3'
    compile ratpack.dependency('h2')
}

distDocker {
    exposePort(5050)
}

With this you can now try out the application by running

$> gradle -t run

And you would see something like this in your console:

[main] INFO ratpack.server.RatpackServer - Starting server...
[main] INFO ratpack.server.RatpackServer - Building registry...
[main] INFO ratpack.server.RatpackServer - Initializing 1 services...
[ratpack-compute-1-1] INFO org.flywaydb.core.internal.util.VersionPrinter - Flyway 4.0.3 by Boxfuse
[ratpack-compute-1-1] INFO org.flywaydb.core.internal.dbsupport.DbSupportFactory - Database: jdbc:h2:mem:conversions (H2 1.4)
[ratpack-compute-1-1] INFO org.flywaydb.core.internal.command.DbValidate - Successfully validated 1 migration (execution time 00:00.005s)
[ratpack-compute-1-1] INFO org.flywaydb.core.internal.metadatatable.MetaDataTableImpl - Creating Metadata table: "PUBLIC"."schema_version"
[ratpack-compute-1-1] INFO org.flywaydb.core.internal.command.DbMigrate - Current version of schema "PUBLIC": << Empty Schema >>
[ratpack-compute-1-1] INFO org.flywaydb.core.internal.command.DbMigrate - Migrating schema "PUBLIC" to version 1 - create conversion table
[ratpack-compute-1-1] INFO org.flywaydb.core.internal.command.DbMigrate - Successfully applied 1 migration to schema "PUBLIC" (execution time 00:00.038s).
[main] INFO ratpack.server.RatpackServer - Ratpack started for http://localhost:5050

You can see the application starting, Flyway doing the migrations and finally if you point your browser to for example http://localhost:5050/convert/EUR/SEK/100 you should get the conversion rate. So it works!

This is nice for development as you can use this way to try it out locally before deploying it to docker. I also used the -t-switch which will allow hot-swapping any changes immediately without re-running the task.

Next lets look into how to make a docker image out of it.

We added the docker plugin and configured it to expose the 5050 port so all that is left is to build the image. That can simply be done by running the following command:

$> gradle distDocker

Here is some of the output of that:

Task :distDocker
Sending build context to Docker daemon 21.06 MB

Step 1/4 : FROM aglover/java8-pier
 ---> 3f3822d3ece5
Step 2/4 : EXPOSE 5050
 ---> Using cache
 ---> 77ed4f7913f9
Step 3/4 : ADD example.tar /
 ---> 765aff2cd114
Removing intermediate container 4eaaf85dd4b2
Step 4/4 : ENTRYPOINT /example/bin/example
 ---> Running in d8046bea895f
 ---> a81175d6b690
Removing intermediate container d8046bea895f
Successfully built a81175d6b690

As you see the distDocker task will create the image and deploy it to the local docker image registry.

Note: You will probably be running the distDocker command using a CI and you most likely want to deploy to your internal docker registry instead of locally. To do that you can set the docker.registry parameter in your build.gradle to point it to the correct registry. More information can be found at https://github.com/Transmode/gradle-docker.

Once we have it, we can run the docker image simply by running

$> docker run -p 5050:5050 a81175d6b690

And that will run your EP via docker!

End notes

Now you know how to build an Groovy REST service without using a single annotation. How does it feel? Was it hard? Easy? Give me comments below.

All the code and examples can be found on Github, here are the links

https://github.com/devsoap/examples/tree/master/currency-converter-ratpack-dsl

https://github.com/devsoap/examples/tree/master/currency-converter-ratpack-ext

Thanks for reading, hope you found it useful!