Hello, Coherence — Now with Micronaut!
Last year, my colleague Aleks Seovic released a series of articles on building a simple task management system using Coherence and Helidon MP. Since then, we’ve been busy improving our integrations with other frameworks, such as Spring and Micronaut.
For those not familiar, Micronaut is a:
“….modern, JVM-based, full stack Java framework designed for building modular, easily testable JVM applications with support for Java, Kotlin, and Groovy.”
We’ll use a similar
Task class as our model, the Coherence Micronaut Data integration as our data store, and a Micronaut controller for the needed REST endpoints.
I am going to avoid getting mired in build details, so I’ll just show you the dependencies we’ll be using:
Preparing the Model
In order for our Micronaut Data repository to be able to use the Task as a model, we’ll need to add two annotations:
@MappedEntity, which identifies
Task as a persisted type, and
@Id, which denotes the ID of that entity.
Coherence-based Micronaut Data Repository
The Coherence integration offers a good blend of the usual CRUD methods, as well as rich query API that may be used either programmatically — which requires some knowledge of Coherence Filters — or via Micronaut Data’s query capabilities requiring no explicit knowledge of Coherence. I’ll show an example of both as we progress. For now, let’s continue with just a basic repository:
As this is a Coherence-based repository, we need to include the name of the Coherence
NamedMap that backs this repository. On line 9, we provide this name as metadata in
@CoherenceRepository annotation. That’s it! That’s a fully functional repository ready to be used by the REST service we’ll create next.
The primary goal that we have for this REST interface is the need to expose the same API as was originally designed in Aleks’ article. This would allow us to use our back-end with the original UI. To this end, we’ll use Micronaut’s
Controller and related annotations to create our REST service.
We’ve defined our class,
ToDoController, as a Micronaut Controller that will respond to requests made to the base path
/api/tasks. We’ve also injected our
TaskRepository that this service will use to response to REST calls.
Let’s go through each of the endpoints we’ll need starting with our basic query method:
So, the intent of this method is such that if there is no query parameter named
completed, then return all stored tasks. Otherwise, return the tasks that match the provided completion status. As it’s difficult to express this using Micronaut Data generated query methods, we’ve used Coherence APIs directly.
In lines 5–7, we define a Coherence Filter that satisfies the required criteria. If there is no
completed query parameter defined, then the filter criteria is
always, and all tasks will be returned. Otherwise, we create a filter that will return only tasks whose
completed property is equal to the provided value.
Lastly, on line 9, we obtain all the tasks using the filter, ordering by the task creation time.
Next, let’s look at our creation endpoint:
Fairly straightforward, we have a POST that produces and consumes JSON. Equally straightforward is the implementation. No additions to the repository are necessary.
For task removals, we have two modes we need to deal with. We’ll start with removing tasks by ID:
Again, nothing overly complex and no additional requirements on the repository.
Now, the other task removal mode is removing all completed tasks. Here, we have a couple of choices.
The first method shows how to do this with a Coherence
The second method is using a Micronaut Data finder query which we would add to our repository definition:
deleteByCompletedTrue is named based on the query DSL defined by Micronaut Data. As can be gleaned from the name, it will delete tasks where the completed property is
deleteCompletedTasks method in our Controller would become:
We need to also support updating a task. This would involve updating either the description or the completion status:
The PUT here takes an ID defined in the path and a JSON payload to update an existing task. However, how this is done is one of the benefits of Coherence as a storage back end; it can update data in-place on the server without the need to marshal the complete object in a round-trip. Lines 9 and 13 accomplish this by calling
update on the repository, giving the ID of the task to update, the method to use to update the
Task::setCompleted), and the new value.
Tying it All Together
Okay, we have our model, repository, and controller ready. How do we bootstrap the application with Micronaut? It’s pretty simple — we’ll need our application entry point and a configuration file. Let’s start with the configuration file,
application.yml, which, by convention, will be included in the JAR file’s resource path.
The first section, labeled
micronaut, configures the Micronaut infrastructure. For more details, refer to Micronaut’s config documentation. But it should be fairly obvious that Micronaut is going to start something on port
3000 — and our controller should be listening.
Next we define a coherence cluster member that will be a part of the
task-cluster and is a storage-enabled member of this cluster — meaning it can store our tasks.
Does it work?
From our Maven project, we can simply run
gradle run to start the example:
The Micronaut banner is printed, and we can see Coherence initializing, so we’re off to a good start.
Coherence services are started and the REST endpoints are ready on port 3000.
We can use the same set of cURL commands as Aleks used to verify the results:
And now, verify the result:
As you can see, we were able to accomplish quite a bit with very little code. There are other parts of the Aleks’ original demo’s that weren’t covered here as they were out of scope; topics such as the UI, Server Sent Events, Kubernetes, etc. These features are present in the GitHub repository for the complete demo, which we encourage readers to explore and learn.
We hope you find this article useful and with that, I can mark another task off my list!