Binding, Blocking and Configuration
Learn binding, blocking, and configuration operations in Ratpack.
We'll cover the following
Bindings
Bindings in Ratpack make objects available to the handlers. If you are familiar with Spring, Ratpack uses a Registry, which is similar to the application context in Spring. It can also be thought of as a simple map from class-types to instances. Ratpack-groovy uses Guice by default, although other direct-injection frameworks can be used (or none at all).
Instead of a formal plugin system, reusable functionality can be packaged as modules. You can create your own modules to properly decouple and organize your application into components. For example, you might want to create a MongoModule
or a JdbcModule
.
Alternatively, you could break your application into services. Either way, the registry is where you put them.
Anything in the registry can be automatically used in a handler and gets wired in by class-type from the registry. The following is an example of bindings in action:
bindings {
bindInstance(MongoModule, new MongoModule())
bind(DragonService, DefaultDragonService)
}
handlers {
get('dragons') {
DragonService dService - >
dService.list().then {
dragons - >
render(toJson(dragons))
}
}
}
Blocking
Blocking operations should be handled by using the “blocking” API. A blocking operation is anything that is IO-bound, such as querying the database.
The Blocking
class is located at ratpack.exec.Blocking
. For example, the following handler calls a
method on the database:
get("deleteOlderThan/:days") {
int days = pathTokens.days as int
Blocking.get { database.deleteOlderThan(days)}
.then { int i - > render("$i records deleted")}
}
You can chain multiple blocking calls using the then
method. The result of the previous closure is
passed as the parameter to the next closure (an int above).
Ratpack handles the thread scheduling for you and then joins with the original computation thread. This way, you can rejoin with the original HTTP request thread and return a result.
get("deleteOlderThan/:days") {
int days = pathTokens.days as int
int result
Blocking.get { database.deleteOlderThan(days) }
.then { int count -> result = count }
render("$result records deleted")
}
If no return value is required, use the Blocking.exec
method.
Configuration
Any self-respecting web application should allow configuration to come from multiple locations: the application, the file-system, environment variables, and system properties. This is a good practice in general but especially for cloud-native apps.
Ratpack includes a built-in configuration API. The ratpack.config.ConfigData
class allows you to
layer multiple sources of configuration. Using the of method with a passed closure allows you to
define a factory of sorts for configuration from JSON, YAML, and other sources.
First, you define your configuration classes. These class properties will define the names of your configuration properties. For example:
class Config {
DatabaseConfig database
}
class DatabaseConfig {
String username = "root"
String password = ""
String hostname = "localhost"
String database = "myDb"
}
In this case, your JSON configuration might look like the following:
{
"database": {
"username": "user",
"password": "changeme",
"hostname": "myapp.dev.company.com"
}
}
Later, in the binding declaration, add the following to bind the configuration defined by the above classes:
serverConfig {
json(getResource("/config.json"))
yaml(getResource("/config.yml"))
sysProps()
env()
}
Each declaration overrides the previous declarations. So, in this case, the order would be: “class definition,” config.json
, config.yml
, system-properties, and then environment variables. This way, you
could override properties at runtime.
System property and environment variable configuration must be prefixed with the ratpack
and
RATPACK_
prefixes accordingly. For example, the hostname property above would be ratpack.database.hostname
as a system property and RATPACK_DATABASE__HOSTNAME
for an environment variable.
Get hands-on with 1200+ tech skills courses.