Creating a Micronaut app with Micronaut Data, Liquibase and Testcontainers

Oscar Hernandez
5 min readSep 9, 2019

--

Starting a new project is always a chance to play around with new technologies. I’ve been anxious to properly give a try to Micronaut ever since its announcement on Greach 2018 and besides a small CLI app I haven’t really had the chance yet. Since then, the team behind Micronaut has also released Micronaut Data (formerly Predator), which aims to combine the ease of use of Spring Data to the lightning speed and compile-time magic of Micronaut. So when I was starting a new project, it was clear I needed to use both of these.

As these technologies are fairly new, theres not much information about them online yet and at times it was hard to find a way to properly combine everything I needed for what a basic production-ready stack.

The goal of this post is to bring together and aggregate what I learned during the creation of a basic app skeleton and create a guide for others to save the time I spent getting it all working.

Create basic empty Micronaut app

All right, let’s start from the top. Assuming you have SDKMan installed, let’s install the Micronaut CLI tools.

sdk install micronaut 1.2.1

and create a Groovy Micronaut app (you can use Java or Kotlin, the entire process should be similar)

mn create-app cloud.osasoft.micronaut.example.basicExample --lang=groovy

This will createa Micronaut app in a subfolder called basicExample with the base package being cloud.osasoft.micronaut.example

Set up Micronaut Data for usage with MySQL

Next up, let’s get the dependencies we’ll need. These are going to be the dependencies for Micronaut Data nad MySQL.

Set the Micronaut Data version in gradle.properties by adding the following line:

micronautDataVersion=1.0.0.M1

and let’s add these dependencies to our build.gradle :

compileOnly "io.micronaut.data:micronaut-data-processor:$micronautDataVersion"
implementation "io.micronaut.data:micronaut-data-jdbc:$micronautDataVersion"
runtimeOnly "io.micronaut.configuration:micronaut-jdbc-tomcat"
compileOnly 'jakarta.persistence:jakarta.persistence-api:2.2.2'
implementation 'mysql:mysql-connector-java:8.0.17'
implementation "io.micronaut.configuration:micronaut-jdbc-hikari"

In brief, above we have the dependencies for Micronaut Data itself and then we have the MySQL Java connector and Hikari for connection pooling.

In application.yml we can now set up the connection simply with

datasources:
default:
url: jdbc:mysql://localhost:3306/example
username: sa
password: sa
dialect: MYSQL

Set up Liquibase for migrations (and using Groovy DSL)

Next, we want to get Liquibase up and running to handle our database migrations. Since I prefer the Groovy DSL for Liquibase over XML, I’ll also be adding the liquibase-groovy-dsl library. We just add the following dependencies:

implementation 'io.micronaut.configuration:micronaut-liquibase'
implementation 'org.liquibase:liquibase-groovy-dsl:2.0.3'

Create root changelog file

Now let’s create the default root changelog file. Let’s create it as resources/db/liquibase-changelog.groovy :

package db

databaseChangeLog {

}

and leave it empty for now. In here we will reference the migration changelog files we want applied. At the time of writing, to my knowledge there is no support for auto-generated changelogs for Micronaut Data, such like what is provided by the Grails Liquibase plugin for GORM, so any migration files we will have to write ourselves, but thats later.

Configure Liquibase

In order for the Liquibase migrations to run, we need to configure them in application.yml

jpa:
default:
properties:
hibernate:
hbm2ddl:
auto: validate

liquibase:
datasources:
default:
change-log: 'classpath:db/liquibase-changelog.groovy'

The JPA configuration part just tells Hibernate not to do any changes to the DB based on the model, we want to leave that to Liquibase. auto:none is also a valid option here.
Liquibase config just points it to our created root changelog file.

At this point, if we run the app, we should see some output from Liquibase as it creates the DATABASECHANGELOG and other tables it uses to keep track of our migrations.

16:56:57.101 [main] INFO  liquibase.executor.jvm.JdbcExecutor - SELECT * FROM `example`.DATABASECHANGELOG ORDER BY DATEEXECUTED ASC, ORDEREXECUTED ASC
16:56:57.102 [main] INFO liquibase.executor.jvm.JdbcExecutor - SELECT COUNT(*) FROM `example`.DATABASECHANGELOGLOCK
16:56:57.107 [main] INFO l.lockservice.StandardLockService - Successfully released change log lock
16:56:57.512 [main] INFO io.micronaut.runtime.Micronaut - Startup completed in 2290ms. Server Running: http://localhost:8080

Create a basic entity

Now let’s create our first migration. For that, lets create a simple entity Person.groovy

@Entity
@CompileStatic
class Player {

@Id
@AutoPopulated
UUID id

@DateCreated
Date dateCreated

@DateUpdated
Date dateUpdated

String firstName
String lastName
int yearOfBirth
}

and a migration to go with it in resources/db/migrations.initialSchema.groovy

package db.migrations

databaseChangeLog {
changeSet(author: "oscar", id: "1") {
createTable(tableName: "person") {
column(name: "id", type: "UUID") {
constraints(primaryKey: "true")
}

column(name: "date_created", type: "DATETIME") {
constraints(nullable: "false")
}
column(name: "date_updated", type: "DATETIME") {
constraints(nullable: "false")
}

column(name: "first_name", type: "VARCHAR(50)")
column(name: "last_name", type: "VARCHAR(50)") column(name: "year_of_birth", type: "INT") {
constraints(nullable: "false")
}
}
}
}

and let the root changelog know we want to apply this migration

package db

databaseChangeLog {
include file: 'migrations/initialschema.groovy', relativeToChangelogFile: true
}

Running the app now, we should see Liquibase creating the new table

17:08:37.251 [main] INFO  liquibase.changelog.ChangeSet - Table person created
17:08:37.251 [main] INFO liquibase.changelog.ChangeSet - ChangeSet classpath:db/migrations/initialschema.groovy::1::oscar ran successfully in 14ms

With this, our basic app is ready to go! Now let’s set up tests.

Add and configure Testcontainers and Spock

I use Spock as my testing framework of choice, but Junit config should be fairly similar. Testcontainers are great in that it allows our tests to run against the actual DB implementation the final product will, so we can avoid some incompatibility issues with, for example, H2 that might pop up.

First, let’s add the Testcontainers Spock and MySQL dependencies

testCompile "org.testcontainers:spock:1.12.1"
testCompile "org.testcontainers:mysql:1.12.1"

and let’s create a very basic test, just to see that our application context starts up properly

@MicronautTest
class ExampleApplicationTest extends Specification {

@Inject
ApplicationContext applicationContext

void setup() {
}

void cleanup() {
}

def "Application starts"() {
expect:
applicationContext
}
}

If we run the test now, it will try and connect to the same database we defined previously, so let’s create a test config under test resources called application-test.yml

datasources:
default:
url: jdbc:tc:mysql://localhost/test
driverClassName: org.testcontainers.jdbc.ContainerDatabaseDriver
username: test
password: test

Since the url contains :tc:mysql and we specified the driver being Testcontainers, a Docker image of MySQL will be spun up automatically along with the application context, and the app will connect to it. test:test are the default credentials for Testcontainers MySQL image. You can configure these as well as the MySQL version to be used (see the docs)

Running the test now, we should see a happy whale in the output:

17:25:56.606 [Test worker] INFO  🐳 [mysql:5.7.22] - Container mysql:5.7.22 started

And there we have it. A fully functional skeleton of a Micronaut + Micronaut Data application with Liquibase migrations and Testcontainers for our database during testing.

Sources

Here’s a (nonexhaustive) list of sources I used when setting the whole thing up:

https://docs.micronaut.io/latest/guide/index.html
https://micronaut-projects.github.io/micronaut-data/1.0.x/guide/

https://micronaut-projects.github.io/micronaut-liquibase/latest/guide/index.html
https://www.testcontainers.org/modules/databases/
https://www.liquibase.org/quickstart/quickstart_lb.html
https://github.com/micronaut-projects/micronaut-data/tree/master/examples
https://github.com/micronaut-projects/micronaut-examples

--

--