Over the past year, the Micronaut framework has become extremely popular. And for good reason, too. It's a pretty revolutionary framework for the JVM world that uses compile time dependency injection and AOP that does not use any reflection. That means huge gains for your startup and runtime performance and memory consumption. But it's not enough to just be performant, a framework has to be easy to use and well documented. The good news is, Micronaut is both of these. And it's fun to use and works great with Groovy, Kotlin and GraalVM. In addition, the people behind Micronaut understand the direction that the industry is heading and have built the framework with that direction in mind. This means that things like Serverless and Cloud deployments are easy and there are features that provide direct support for them.
In this post we'll look at how to create a Microservice with Micronaut which will expose a "Person" API. The service will utilize GORM which is a "data access toolkit" - a fancy way of saying it's a really easy way to work with databases (from traditional RDBMS to MongoDB, Neo4J and more). Specifically, we'll utilize GORM for Hibernate to interact with an Oracle Autonomous Transaction Processing DB. Here's what we'll be doing:
First things first, make sure you have an Oracle ATP instance up and running. Luckily, that's really easy to do and this post by my boss Gerald Venzl will show you how to set up an ATP instance in less than 5 minutes. Once you have a running instance, grab a copy of your Client Credentials "Wallet" and unzip it somewhere on your local system.
Before we move on to the next step, create a new schema in your ATP instance and create a single table using the following DDL:
You're now ready to move on to the next step, creating the Micronaut application.
If you've never used it before, you'll need to install Micronaut which includes a helpful CLI for scaffolding certain elements like the application itself and controllers, etc as you work with your application. Once you've confirmed the install, run the following command to generate your basic application:
Take a look inside that directory to see what the CLI has generated for you.
As you can see, the CLI has generated a Gradle build script, a Dockerfile and some other config files as well as a
src directory. That directory looks like this:
At this point you can import the application into your favorite IDE, so do that now. The next step is to generate a controller:
We'll make one small adjustment to the generated controller, so open it up and add the
@CompileStatic annotation to the controller. It should like so once you're done:
Now run the application using
gradle run (we can also use the Gradle wrapper with
./gradlew run) and our application will start up and be available via the browser or a simple curl command to confirm that it's working. You'll see the following in your console once the app is ready to go:
Give it a shot:
We aren't returning any content, but we can see the
200 OK which means the application received the request and returned the appropriate response.
To make things easier for development and testing the app locally I like to create a custom Run/Debug configuration in my IDE (IntelliJ IDEA) and point it at a custom Gradle task. We'll need to pass in some System properties eventually, and this enables us to do that when launching from the IDE. Create a new task in
myTask that looks like so:
Now create a custom Run/Debug configuration that points at this task and add the VM options that we'll need later on for the Oracle DB connection:
Here are the properties we'll need to populate for easier copy/pasting:
Let's move to the next step and get the application ready to talk to ATP!
Before we can configure the application we need to make sure we have the Oracle JDBC drivers available.
Note: When this post was originally published, it indicated that you would have to download the OJDBC drivers and manually include them. This is no longer necessary - you can simply add the OJDBC Maven dependency as shown on line 3 below.
dependencies block in your
build.gradle file so that the Oracle JDBC JARs and the
micronaut-hibernate-gorm artifacts are included as dependencies:
Now let's modify the file located at
src/main/resources/application.yml to configure the datasource and Hibernate.
Our app is now ready to talk to ATP via GORM, so it's time to create a service, model and some controller methods! We'll start with the model.
GORM models are super easy to work with. They're just POGO's (Plain Old Groovy Objects) with some special annotations that help identify them as model entities and provide validation via the Bean Validation API. Let's create our
Person model object by adding a Groovy class called
Person.groovy in a new directory called
model. Populate the model as such:
Take note of a few items here. We've annotated the class with
grails.gorm.annotation.Entity) so GORM knows that this is an entity it needs to manage. Our model has 3 properties:
isCool. If you look back at the DDL we used to create the
person table above you'll notice that we have two additional columns that aren't addressed in the model:
ID column is implicit with a GORM entity and the
version column is auto-managed by GORM to handle optimistic locking on entities. You'll also notice a few annotations on the properties which are used for data validation as we'll see later on.
We can start the application up again at this point and we'll see that GORM has identified our entity and Micronaut has configured the application for Hibernate:
Let's move on to creating a service.
I'm not going to lie to you. If you're waiting for things to get difficult here, you're going to be disappointed. Creating the service that we're going to use to manage
Person CRUD operations is really easy to do. Create a Groovy class called
PersonService in a new directory called
service and populate it with the following:
That's literally all it takes. This service is now ready to handle operations from our controller. GORM is smart enough to take the method signatures that we've provided here and implement the methods. The nice thing about using an abstract class approach (as opposed to using the interface approach) is that we can manually implement the methods ourselves if we have additional business logic that requires us to do so.
There's no need to restart the application here, as we've made no changes that would be visible at this point. We're going to need to modify our controller for that, so let's create one!
Let's modify the
PersonController that we created earlier to give us some endpoints that we can use to do some persistence operations. First, we'll need to inject our
PersonService into the controller. This too is straightforward by simply including the following just inside our class declaration:
The first step in our controller should be a method to save a
Person. Let's add a method annotated with
@Post to handle this and within the method we'll call the
PersonService.save() method. If things go well, we'll return the newly created
Person, if not we'll return a list of validation errors. Note that Micronaut will bind the body of the HTTP request to the
person argument of the controller method meaning that inside the method we'll have a fully populated
Person bean to work with.
If we start up the application we are now able to persist a
Person via the
Note that we've received a
200 OK response here with an object containing our
Person. However, if we tried the operation with some invalid data, we'd receive some errors back:
Since our model (very strangely) indicated that the
firstName must be between 5 and 50 characters we receive a
422 Unprocessable Entity response that contains an array of validation errors back with this response.
Now we'll add a
/list endpoint that users can hit to list all of the
Person objects stored in the ATP instance. We'll set it up with two optional parameters that can be used for pagination.
Remember that our
PersonService had two signatures for the
findAll method - one that accepted no parameters and another that accepted a
Map signature can be used to pass additional parameters like those used for pagination. So calling
/person/list without any parameters will give us all
Or we can get a subset via the pagination params like so:
We can also add a `/person/get` endpoint to get a `Person` by ID:
/person/delete endpoint to delete a
We've seen here that Micronaut is a simple but powerful way to create performant Microservice applications and that data persistence via Hibernate/GORM is easy to accomplish when using an Oracle ATP backend. Your feedback is very important to me so please feel free to comment below or interact with me on Twitter (@recursivecodes).
If you'd like to take a look at this entire application you can view it or clone via Github.