1. Overview
Ratpack is a set of lightweight Java libraries for building scalable HTTP applications with reactive, asynchronous, and non-blocking features.
Additionally, Ratpack also provides integration with technologies and frameworks like Google Guice, Spring Boot, RxJava and Hystrix.
In this tutorial, we’ll explore how to use Ratpack with Groovy.
2. Why Groovy?
Groovy is a powerful, dynamic language that runs in the JVM.
Therefore, Groovy makes scripting and Domain Specific Languages really easy. With Ratpack, this provides for rapid web application development.
*Ratpack provides easy integration with Groovy through the ratpack-groovy and *ratpack**-groovy-test libraries.**
3. Ratpack Application Using Groovy Script
The Ratpack Groovy APIs are built in Java so they can easily integrate with both Java and Groovy applications. They’re available in the ratpack.groovy package.
Actually, in combination with Groovy’s scripting abilities and Grape dependency management, we can quickly create a Ratpack-powered web application in just a few lines:
@Grab('io.ratpack:ratpack-groovy:1.6.1')
import static ratpack.groovy.Groovy.ratpack
ratpack {
handlers {
get {
render 'Hello World from Ratpack with Groovy!!'
}
}
}
This is our first handler, handling a GET request. All we had to do was to add some basic DSL to enable the Ratpack server.
Let’s now run this as a Groovy script to start the application. By default, the application will be available on http://localhost:5050:
$ curl -s localhost:5050
Hello World from Ratpack with Groovy!!
We can also configure the port using ServerConfig:
ratpack {
serverConfig {
port(5056)
}
}
Ratpack also provides a hot reloading feature, which means we can change Ratpack.groovy, and then see the changes the moment the application serves our next HTTP request.
4. Ratpack-Groovy Dependency Management
There are several ways to enable ratpack-groovy support.
4.1. Grape
We can use Groovy’s embedded dependency manager Grape.
It’s as simple as adding an annotation to our Ratpack.groovy script:
@Grab('io.ratpack:ratpack-groovy:1.6.1')
import static ratpack.groovy.Groovy.ratpack
4.2. Maven Dependency
For building in Maven, all we need is to add the dependency for the ratpack-groovy library:
<dependency>
<groupId>io.ratpack</groupId>
<artifactId>ratpack-groovy</artifactId>
<version>${ratpack.version}</version>
</dependency>
4.3. Gradle
We can enable ratpack-groovy integration, by adding Ratpack’s Gradle plugin for Groovy in build.gradle:
plugins {
id 'io.ratpack.ratpack-groovy' version '1.6.1'
}
5. Ratpack Handlers in Groovy
Handlers provide a way to handle web requests and responses. The request and response objects can be accessed in this closure.
We can handle web requests using HTTP methods like GET and POST*:*
handlers {
get("greet/:name") { ctx ->
render "Hello " + ctx.getPathTokens().get("name") + " !!!"
}
}
We can test this web request through http://localhost:5050/greet/
$ curl -s localhost:5050/greet/Norman
Hello Norman!!!
In the code of the handler, ctx is the Context registry object which grants access to path variables, request and response objects.
Handlers also have support for dealing with JSON through Jackson.
Let’s return JSON, converted from a Groovy map:
get("data") {
render Jackson.json([title: "Mr", name: "Norman", country: "USA"])
}
$ curl -s localhost:5050/data
{"title":"Mr","name":"Norman","country":"USA"}
Here, Jackson.json is used to make the conversion.
6. Ratpack Promises in Groovy
As we know, Ratpack enables the asynchronous and non-blocking features in the application. This is implemented with Ratpack Promises.
Promises are similar to those used in JavaScript and are a bit like a Java Future. We can think of a Promise as the representation of a value which will be available in the future:
post("user") {
Promise<User> user = parse(Jackson.fromJson(User))
user.then { u -> render u.name }
}
The last action here is the then action, which determines what to do with the final value. In this case, we return it as a response to the POST.
Let’s understand this code in more detail. Here, Jackson.fromJson parses the JSON of the request body using the ObjectMapper User. Then, the inbuilt Context.parse method binds it to the Promise object.
The promise operates asynchronously. When the then operation is eventually performed, the response is returned:
curl -X POST -H 'Content-type: application/json' --data \
'{"id":3,"title":"Mrs","name":"Jiney Weiber","country":"UK"}' \
http://localhost:5050/employee
Jiney Weiber
We should note that the Promise library is quite rich, allowing us to chain actions using functions like map and flatMap.
7. Integration with a Database
Having asynchronous handlers is of most benefit when our handlers have to wait for services. Let’s demonstrate this by integrating our Ratpack application with an H2 database.
We can either use the Ratpack’s HikariModule class which is an extension of HikariCP JDBC connection pool, or Groovy Sql for database integration.
7.1. HikariModule
To add HikariCP support, let’s first add the following Hikari and H2 maven dependencies in our pom.xml:
<dependency>
<groupId>io.ratpack</groupId>
<artifactId>ratpack-hikari</artifactId>
<version>${ratpack.version}</version>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<version>${h2.version}</version>
</dependency>
Or, we can add the following dependencies to our build.gradle:
dependencies {
compile ratpack.dependency('hikari')
compile "com.h2database:h2:$h2.version"
}
Now, we’ll declare HikariModule under the bindings closure for the connection pool:
import ratpack.hikari.HikariModule
ratpack {
bindings {
module(HikariModule) { config ->
config.dataSourceClassName = 'org.h2.jdbcx.JdbcDataSource'
config.addDataSourceProperty('URL',
"jdbc:h2:mem:devDB;INIT=RUNSCRIPT FROM 'classpath:/User.sql'")
}
}
}
Finally, we’re all set to use it for simple database operations using Java’s Connection and PreparedStatement:
get('fetchUserName/:id') { Context ctx ->
Connection connection = ctx.get(DataSource.class).getConnection()
PreparedStatement queryStatement =
connection.prepareStatement("SELECT NAME FROM USER WHERE ID=?")
queryStatement.setInt(1, Integer.parseInt(ctx.getPathTokens().get("id")))
ResultSet resultSet = queryStatement.executeQuery()
resultSet.next()
render resultSet.getString(1)
}
Let’s check that the handler works as expected:
$ curl -s localhost:5050/fetchUserName/1
Norman Potter
7.2. Groovy Sql Class
We can use Groovy Sql for quick database operations, through methods like rows and executeInsert:
get('fetchUsers') {
def db = [url:'jdbc:h2:mem:devDB']
def sql = Sql.newInstance(db.url, db.user, db.password)
def users = sql.rows("SELECT * FROM USER");
render(Jackson.json(users))
}
$ curl -s localhost:5050/fetchUsers
[{"ID":1,"TITLE":"Mr","NAME":"Norman Potter","COUNTRY":"USA"},
{"ID":2,"TITLE":"Miss","NAME":"Ketty Smith","COUNTRY":"FRANCE"}]
Let’s write an HTTP POST example with Sql:
post('addUser') {
parse(Jackson.fromJson(User))
.then { u ->
def db = [url:'jdbc:h2:mem:devDB']
Sql sql = Sql.newInstance(db.url, db.user, db.password)
sql.executeInsert("INSERT INTO USER VALUES (?,?,?,?)",
[u.id, u.title, u.name, u.country])
render "User $u.name inserted"
}
}
$ curl -X POST -H 'Content-type: application/json' --data \
'{"id":3,"title":"Mrs","name":"Jiney Weiber","country":"UK"}' \
http://localhost:5050/addUser
User Jiney Weiber inserted
8. Unit Testing
8.1. Setting up the Tests
As discussed, Ratpack also provides the ratpack**-groovy-test library for testing a ratpack-groovy application.
To use it, we can add it as Maven dependency in our pom.xml:
<dependency>
<groupId>io.ratpack</groupId>
<artifactId>ratpack-groovy-test</artifactId>
<version>1.6.1</version>
</dependency>
Alternatively, we can add the Gradle dependency in our build.gradle:
testCompile ratpack.dependency('groovy-test')
Then we need to create a Groovy main class RatpackGroovyApp.groovy to let us test the Ratpack.groovy script.
public class RatpackGroovyApp {
public static void main(String[] args) {
File file = new File("src/main/groovy/com/baeldung/Ratpack.groovy");
def shell = new GroovyShell()
shell.evaluate(file)
}
}
When running Groovy tests as JUnit tests, the class will invoke the Ratpack.groovy script using GroovyShell. In turn, it will start the Ratpack server for testing.
Now, let’s write our Groovy Test class RatpackGroovySpec.groovy along with the code to start the Ratpack server through the RatpackGroovyApp:
class RatpackGroovySpec {
ServerBackedApplicationUnderTest ratpackGroovyApp =
new MainClassApplicationUnderTest(RatpackGroovyApp.class)
@Delegate TestHttpClient client =
TestHttpClient.testHttpClient(ratpackGroovyApp)
}
Ratpack provides MainClassApplicationUnderTest to mock the application class for starting the server.
8.2. Writing Our Tests
Let’s write our tests, starting with a very basic test to check if the application can start:
@Test
void "test if app is started"() {
when:
get("")
then:
assert response.statusCode == 200
assert response.body.text ==
"Hello World from Ratpack with Groovy!!"
}
Let’s now write another test to verify the response of the fetchUsers get handler:
@Test
void "test fetchUsers"() {
when:
get("fetchUsers")
then:
assert response.statusCode == 200
assert response.body.text ==
'[{"ID":1,"TITLE":"Mr","NAME":"Norman Potter","COUNTRY":"USA"},{"ID":2,"TITLE":"Miss","NAME":"Ketty Smith","COUNTRY":"FRANCE"}]'
}
The Ratpack test framework takes care of starting and stopping the server for us.
9. Conclusion
In this article, we’ve seen a few ways to write HTTP handlers for Ratpack using Groovy. We also explored Promises and Database integration.
We’ve seen how Groovy closures, DSLs, and Groovy’s Sql make our code concise, efficient and readable. At the same time, Groovy’s test support makes unit and integration testing straightforward.
With these techniques, we can use Groovy’s dynamic language features, and expressive APIs, to rapidly develop high-performance, scalable HTTP applications with Ratpack.
As usual, the example code can be found over on GitHub.