Search

Dark theme | Light theme
Showing posts with label Ratpacked:Groovy. Show all posts
Showing posts with label Ratpacked:Groovy. Show all posts

March 7, 2017

Ratpacked: Type Check And Static Compilation For Groovy DSL

One of the very nice features of Ratpack is the Groovy DSL to define our application. We get a nice DSL to set up the registry, to define handlers and more. Because of clever use of the @DelegateTo annotation we get good code completion in our IDE. We can also add static compilation of our Groovy DSL when we start our Ratpack application. With static compilation the script is type checked at compile time so we get earlier feedback on possible errors in the script. To configure static compilation we must invoke the app method of the Groovy.Script class with the argument true.

We start with a Groovy DSL for an application that serves recipes. Notice the Closure arguments are all typed, so with type checking there are no errors.

// File: src/ratpack/ratpack.groovy
import mrhaki.ratpack.Recipe
import mrhaki.ratpack.RecipeRenderer
import mrhaki.ratpack.RecipeRepository
import mrhaki.ratpack.RecipeRequest
import mrhaki.ratpack.RecipesList

import static ratpack.groovy.Groovy.ratpack

ratpack {
    bindings {
        add new RecipeRenderer()
        add RecipeRepository, new RecipesList()
    }

    handlers {
        post('recipe') { RecipeRepository recipeRepository ->
            parse(RecipeRequest)
                    .flatMap { RecipeRequest recipeRequest -> 
                        recipeRepository.findRecipeByName(recipeRequest.name) 
                    }
                    .then { Optional<Recipe> optionalRecipe -> 
                        render(optionalRecipe) 
                    }
        }
    }
}

Next we create a class with a main method that will be the starting point of our application. We need to run this class to start our Ratpack application. The example class is a Java class, but could also be written with Groovy:

// File: src/main/java/mrhaki/sample/GroovyCompileStaticRatpackMain.java
package mrhaki.ratpack;

import ratpack.groovy.Groovy;
import ratpack.server.RatpackServer;

import java.util.Optional;

public class GroovyCompileStaticRatpackMain {
    
    public static void main(String[] args) throws Exception {
        RatpackServer.start(Groovy.Script.app(true /* compileStatic */));
    }
}

When we use the Gradle Ratpack plugin we use this class as the main class:

// File: build.gradle
...
mainClassName = 'mrhaki.ratpack.GroovyCompileStaticRatpackMain'
...

Now we can still use the run task to start our Ratpack application.

Written with Ratpack 1.4.5.

June 27, 2016

Ratpacked: Using Groovy Configuration Scripts As Configuration Source

Ratpack has a lot of options to add configuration data to our application. We can use for example YAML and JSON files, properties, environment variables and Java system properties. Groovy has the ConfigSlurper class to parse Groovy script with configuration data. It even supports an environments block to set configuration value for a specific environment. If we want to support Groovy scripts as configuration definition we write a class that implements the ratpack.config.ConfigSource interface.

We create a new class ConfigSlurperConfigSource and implement the ConfigSource interface. We must implement the loadConfigData method in which we read the Groovy configuration and transform it to a ObjectNode so Ratpack can use it:

// File: src/main/groovy/mrhaki/ratpack/config/ConfigSlurperConfigSource.groovy
package mrhaki.ratpack.config

import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.databind.node.ArrayNode
import com.fasterxml.jackson.databind.node.ObjectNode
import groovy.transform.CompileDynamic
import groovy.transform.CompileStatic
import ratpack.config.ConfigSource
import ratpack.file.FileSystemBinding

import java.nio.file.Path

@CompileStatic
class ConfigSlurperConfigSource implements ConfigSource {
    
    private final String configScript
    
    private final URL scriptUrl
    
    private final String environment
    
    ConfigSlurperConfigSource(final String configScript) {
        this(configScript, '')
    }

    ConfigSlurperConfigSource(final String configScript, final String environment) {
        this.configScript = configScript
        this.environment = environment
    }

    ConfigSlurperConfigSource(final Path configPath) {
        this(configPath, '')
    }

    ConfigSlurperConfigSource(final Path configPath, final String environment) {
        this(configPath.toUri(), environment)
    }

    ConfigSlurperConfigSource(final URI configUri) {
        this(configUri, '')
    }

    ConfigSlurperConfigSource(final URI configUri, final String environment) {
        this(configUri.toURL(), environment)
    }

    ConfigSlurperConfigSource(final URL configUrl) {
        this(configUrl, '')
    }

    ConfigSlurperConfigSource(final URL configUrl, final String environment) {
        this.scriptUrl = configUrl
        this.environment = environment
    }

    @Override
    ObjectNode loadConfigData(
            final ObjectMapper objectMapper, 
            final FileSystemBinding fileSystemBinding) throws Exception {

        // Create ConfigSlurper for given environment.
        final ConfigSlurper configSlurper = new ConfigSlurper(environment)

        // Read configuration.
        final ConfigObject configObject = 
                configScript ? 
                        configSlurper.parse(configScript) : 
                        configSlurper.parse(scriptUrl)
        
        // Transform configuration to node tree
        final ObjectNode rootNode = objectMapper.createObjectNode()
        populate(rootNode, configObject)
        return rootNode
    }

    @CompileDynamic
    private populate(final ObjectNode node, final ConfigObject config) {
        // Loop through configuration.
        // ConfigObject also implements Map interface,
        // so we can loop through key/value pairs.
        config.each { key, value ->
            // Value is another configuration,
            // this means the nested configuration
            // block.
            if (value instanceof Map) {
                populate(node.putObject(key), value)
            } else {
                // If value is a List we convert it to
                // an array node.
                if (value instanceof List) {
                    final ArrayNode listNode = node.putArray(key)
                    value.each { listValue ->
                        listNode.add(listValue)
                    }
                } else {
                    // Put key/value pair in node.
                    node.put(key, value)
                }
            }
        }
    }
}

We have several options to pass the Groovy configuration to the ConfigSlurperConfigSource class. We can use a String, URI, URL or Path reference. Let's create a file with some configuration data.

// File: src/ratpack/application.groovy
app {
    serverPort = 9000
}

environments {
    development {
        app {
            serverName = 'local'
        }
    }
    production {
        app {
            serverName = 'cloud'
            serverPort = 80
        }
    }
}

Next we create a Ratpack application using the Groovy DSL. In the serverConfig section we use our new ConfigSlurperConfigSource:

// File: src/ratpack/ratpack.groovy
import com.google.common.io.Resources
import com.mrhaki.config.ConfigSlurperConfigSource

import static groovy.json.JsonOutput.prettyPrint
import static groovy.json.JsonOutput.toJson
import static ratpack.groovy.Groovy.ratpack

//final Logger log = LoggerFactory.getLogger('ratpack')

ratpack {

    serverConfig {
        // Use Groovy configuration.
        add new ConfigSlurperConfigSource('''\
            app {
                message = 'Ratpack swings!'
            }''')

        // Use external Groovy configuration script file.
        add new ConfigSlurperConfigSource(
                Resources.getResource('application.groovy'), 'development')

        require '/app', SimpleConfig
    }

    handlers {
        get('configprops') { SimpleConfig config ->
            render(prettyPrint(toJson(config)))
        }
    }

}

// Simple configuration.
class SimpleConfig {
    String message
    String serverName
    Integer serverPort
}

Let's check the output of the configprops endpoint:

$ http -b localhost:5050/configprops
{
    "message": "Ratpack swings!",
    "serverName": "local",
    "serverPort": 9000
}

Now we set the environment to production in our Ratpack application:

// File: src/ratpack/ratpack.groovy
...

ratpack {

    serverConfig {
        ...

        // Use external Groovy configuration script file.
        add new ConfigSlurperConfigSource(
                Resources.getResource('application.groovy'), 'production')

       ...
    }

    ...
}

If we check configprops again we see different configuration values:

$ http -b localhost:5050/configprops
{
    "message": "Ratpack swings!",
    "serverName": "cloud",
    "serverPort": 80
}

Written with Ratpack 1.3.3.

January 20, 2016

Ratpacked: Running Ratpack In Groovy Console

It is actually very easy to run a Ratpack application in the Groovy Console. The Groovy Console is a GUI application that is distributed with Groovy and allows us to write and run Groovy scripts. We start the Groovy Console with the groovyConsole command: $ groovyConsole. To run a Ratpack application we only have to add a dependency to Ratpack using the @Grab annotation. We can write an application with the Groovy DSL and select Script | Run from the menu. If we make a change in the script file we invoke the Run command again. The Ratpack application restarts with our changes. This is very useful for trying out some Ratpack features without much hassle.

The following screenshot shows a simple Ratpack application. At the bottom we see the logging output of the running application:

Written with Ratpack 1.1.1.

September 30, 2015

Ratpacked: Groovy DSL Code Completion In IntelliJ IDEA

Ratpack applications can be written in Java and Groovy. The Java API is already very clean and on top is a Groovy DSL to work with Ratpack. When we use Groovy we can use the DSL, which allows for more clean code. The Ratpack developers have used the @DelegateTo annotation in the source code for the DSL definition. The annotation can be used to indicate which class or interface is used as delegate to execute the closure that is passed to the method. And this helps us a lot in the code editor of IntelliJ IDEA, because IDEA uses this information to give us code completion when we use the Groovy DSL in Ratpack. And that makes using the DSL very easy, because we rely on the IDE to give us the supported properties and methods and we make less mistakes.

Let's see this with an example in code. First we create a new Ratpack.groovy file:

// File: src/ratpack/Ratpack.groovy
import static ratpack.groovy.Groovy.ratpack

ratpack {
    handlers {

    }
}

We want to add a method in the closure that is passed to the handlers method. We first type ge and wait for the code completion to come up:

This is great! We get very informative information to complete our DSL. It is nice to see why this work in the source code of Ratpack for the handlers method:

...
    /**
     * Registers the closure used to build the handler chain of the application.
     *
     * @param configurer The configuration closure, delegating to {@link GroovyChain}
     */
    void handlers(@DelegatesTo(value = GroovyChain.class, strategy = Closure.DELEGATE_FIRST) Closure<?> configurer);

...

The @DelegatesTo annotation defines that the delegate of the closure code is the GroovyChain class. IntelliJ IDEA now knows that GroovyChain is used and gives us via the code completion the methods and properties of GroovyChain.

Written with Ratpack 1.0.0.