Groovy / Gradle

Using EclipseStore persistence with Micronaut

Learn how to use EclipseStore as a high-performance persistence layer.

Tim Yates, Sergio del Amo
On this guide
In this section

Getting Started

In this guide, we will create a Micronaut application written in Groovy.

You will use EclipseStore for persistence.

What you will need

To complete this guide, you will need the following:

Solution

We recommend that you follow the instructions in the next sections and create the application step by step. However, you can go right to the completed example.

Writing the Application

Create an application using the Micronaut Command Line Interface or with Micronaut Launch.

mn create-app example.micronaut.micronautguide \
    --features=properties,eclipsestore,serialization-jackson,validation \
    --build=gradle \
    --lang=groovy \
    --test=spock
Note
If you don’t specify the --build argument, Gradle with the Kotlin DSL is used as the build tool.
If you don’t specify the --lang argument, Java is used as the language.
If you don’t specify the --test argument, JUnit is used for Java and Kotlin, and Spock is used for Groovy.

The previous command creates a Micronaut application with the default package example.micronaut in a directory named micronautguide.

If you use Micronaut Launch, select Micronaut Application as application type and add properties, eclipsestore, serialization-jackson, and validation features.

Note
If you have an existing Micronaut application and want to add the functionality described here, you can view the dependency and configuration changes from the specified features, and apply those changes to your application.

Dependencies

The eclipsestore feature adds the following dependencies:

build.gradle
compileOnly("io.micronaut.eclipsestore:micronaut-eclipsestore-processor")
compileOnly("io.micronaut.eclipsestore:micronaut-eclipsestore-processor")
implementation("io.micronaut.eclipsestore:micronaut-eclipsestore")

Domain object

Create a Fruit class which will be used as the domain object.

groovy/src/main/groovy/example/micronaut/Fruit.groovy

Root Object

Create a FruitContainer POGO which will be used as the root of our object graph.

groovy/src/main/groovy/example/micronaut/FruitContainer.groovy
package example.micronaut

import io.micronaut.core.annotation.NonNull

import java.util.concurrent.ConcurrentHashMap

class FruitContainer {

    @NonNull
    final Map<String, Fruit> fruits = new ConcurrentHashMap<>()
}

Configuration

Add the following snippet to application.properties to configure EclipseStore.

src/main/resources/application.properties
eclipsestore.storage.main.root-class=example.micronaut.FruitContainer
eclipsestore.storage.main.storage-directory=build/fruit-storage

Command object

And a FruitCommand class which will be used as the command object over HTTP.

groovy/src/main/groovy/example/micronaut/FruitCommand.groovy

Repository

Create a repository interface to encapsulate the CRUD actions for Fruit.

groovy/src/main/groovy/example/micronaut/FruitRepository.groovy

Error handling

In the event an attempt is made to create a duplicate fruit, we will catch the exception with a custom class.

groovy/src/main/groovy/example/micronaut/FruitDuplicateException.groovy
package example.micronaut

class FruitDuplicateException extends RuntimeException{

    FruitDuplicateException(String name) {
        super("Fruit '" + name + "' already exists.")
    }
}

A custom ExceptionHandler handles this exception and returns a 400 error with a sensible message.

groovy/src/main/groovy/example/micronaut/FruitDuplicateExceptionHandler.groovy

Repository implementation

Implement the FruitRepository interface.

When an object in your graph changes, you need to persist the object that contains the change. This can be achieved through the StoreParams and StoreReturn annotations.

groovy/src/main/groovy/example/micronaut/FruitRepositoryImpl.groovy

Controller

Create FruitController:

groovy/src/main/groovy/example/micronaut/FruitController.groovy

Test

Create a test that verifies the validation of the FruitCommand POJO when we invoke the FruitRepository interface:

groovy/src/test/groovy/example/micronaut/FruitRepositorySpec.groovy

Create a test that verifies the validation of the FruitCommand POJO when we create a new entity via POST:

groovy/src/test/groovy/example/micronaut/FruitValidationControllerSpec.groovy

We will use temporary directories to persist our data under test.

To facilitate this, create a base test class that handles the creation of a temporary folder, and configuring the application.

groovy/src/test/groovy/example/micronaut/BaseSpec.groovy
package example.micronaut

import io.micronaut.test.support.TestPropertyProvider
import spock.lang.Specification
import java.nio.file.Files
import java.nio.file.Path
import io.micronaut.core.annotation.NonNull

abstract class BaseSpec extends Specification implements TestPropertyProvider {

    @Override
    @NonNull
    Map<String, String> getProperties() {
        Path tempDir = Files.createTempDirectory('microstream')
        ["microstream.storage.main.storage-directory": tempDir.toString()]
    }
}

Create a test that validates FruitDuplicateExceptionHandler.

groovy/src/test/groovy/example/micronaut/FruitDuplicationExceptionHandlerSpec.groovy

Add a Micronaut declarative HTTP Client to src/test to ease the testing of the application’s API.

groovy/src/test/groovy/example/micronaut/FruitClient.groovy
package example.micronaut

import io.micronaut.core.annotation.NonNull
import io.micronaut.http.HttpResponse
import io.micronaut.http.HttpStatus
import io.micronaut.http.annotation.Body
import io.micronaut.http.annotation.Delete
import io.micronaut.http.annotation.Get
import io.micronaut.http.annotation.PathVariable
import io.micronaut.http.annotation.Post
import io.micronaut.http.annotation.Put
import io.micronaut.http.client.annotation.Client

import jakarta.validation.Valid
import jakarta.validation.constraints.NotBlank
import jakarta.validation.constraints.NotNull

@Client("/fruits")
interface FruitClient {

    @Get
    Iterable<Fruit> list()

    @Get("/{name}")
    Optional<Fruit> find(@NonNull @NotBlank @PathVariable String name)

    @Post
    HttpResponse<Fruit> create(@NonNull @NotNull @Valid @Body FruitCommand fruit)

    @Put
    Optional<Fruit> update(@NonNull @NotNull @Valid @Body FruitCommand fruit)

    @NonNull
    @Delete
    HttpStatus delete(@NonNull @Valid @Body FruitCommand fruit)
}

And finally, create a test that checks the controller works against EclipseStore correctly:

groovy/src/test/groovy/example/micronaut/FruitControllerSpec.groovy

Testing the Application

To run the tests:

./gradlew test

Then open build/reports/tests/test/index.html in a browser to see the results.

Running the Application

To run the application, use the ./gradlew run command, which starts the application on port 8080.

Create a new fruit
curl -i -d '{"name":"Pear"}' \
     -H "Content-Type: application/json" \
     -X POST http://localhost:8080/fruits
Output
HTTP/1.1 201 Created
date: Thu, 12 May 2022 13:45:56 GMT
Content-Type: application/json
content-length: 16
connection: keep-alive

{"name":"Pear"}
Get a list of all fruits
curl -i localhost:8080/fruits
Output
HTTP/1.1 200 OK
date: Thu, 12 May 2022 13:46:54 GMT
Content-Type: application/json
content-length: 70
connection: keep-alive

[{"name":"Pear"}]

EclipseStore REST and GUI

During development, it is often useful to see the data being saved by EclipseStore. Micronaut EclipseStore integration helps you do that.

Add the following dependency:

build.gradle
developmentOnly("io.micronaut.eclipsestore:micronaut-eclipsestore-rest")

The above dependency provides several JSON endpoints which expose the contents of the EclipseStore storage.

We need to enable Micronaut EclipseStore REST endpoints via configuration. For security, they are disabled by default. We will enable them only in the dev environment.

Dev default environment

Modify Application to use dev as a default environment.

groovy/src/main/groovy/example/micronaut/Application.groovy
package example.micronaut

import io.micronaut.core.annotation.NonNull
import io.micronaut.context.ApplicationContextBuilder
import io.micronaut.context.ApplicationContextConfigurer
import io.micronaut.context.annotation.ContextConfigurer
import io.micronaut.runtime.Micronaut
import groovy.transform.CompileStatic

@CompileStatic
class Application {

    @ContextConfigurer
    static class Configurer implements ApplicationContextConfigurer {
        @Override
        public void configure(@NonNull ApplicationContextBuilder builder) {
            builder.defaultEnvironments("dev")
        }
    }
    static void main(String[] args) {
        Micronaut.run(Application, args)
    }
}

Development Environment Configuration

Create src/main/resources/application-dev.properties. The Micronaut framework applies this configuration file only for the dev environment.

src/main/resources/application-dev.properties
eclipsestore.rest.enabled=true

EclipseStore Client GUI

Run the client and connect to the EclipseStore REST API exposed by the Micronaut application:

eclipsestore rest 1

You can visualize the data you saved via curl.

eclipsestore rest 2

Next Steps

Explore more features with Micronaut Guides.

Read more about:

License

Note
All guides are released with an Apache License 2.0 for the code and a Creative Commons Attribution 4.0 license for the writing and media (images).