Introductory workshop about Micronaut
= image:https://raw.githubusercontent.com/micronaut-projects/static-website/gh-pages/images/favicon-32x32.png[] https://alvarosanchez.github.io/micronaut-workshop-java/[Micronaut Workshop] Alvaro Sanchez-Mariscal [email protected] :toc: left :toclevels: 4 :source-highlighter: highlightjs :icons: font :imagesdir: ./images
++++ ++++
Introductory workshop about http://micronaut.io[Micronaut].
== Software Requirements
In order to do this workshop, you need the following:
curl
.wget
.unzip
.git
.consul
.mongo
.=== Micronaut CLI
Install http://sdkman.io[SDKMAN!] if you haven't done so already.
Install Micronaut CLI:
$ sdk install micronaut
Ensure the CLI is installed properly:
$ mn --version | Micronaut Version: 1.1.0 | JVM Version: 1.8.0_191
=== Clone this repository
Once done, you can clone this repo:
NOTE: You will find each exercise's template files on each exNN
folder. Solution is always inside a solution
folder. To highlight the actions you actually need to perform, an icon is used: icon:hand-o-right[]
== Application architecture
Throughout this workshop, we will be creating a football (soccer) management system.
ifdef::generate-diagrams[] [plantuml, football-diagram, png] .... together { node Fixtures database Mongo }
together { node Clubs database H2 }
Clubs -> H2 H2 -[hidden]- Fixtures Fixtures -> Mongo Fixtures -> Clubs .... endif::[]
image::football-diagram.png[]
clubs
is the microservice responsible for managing clubs. It uses Hibernatefixtures
manages all game fixtures, storing its data in MongoDB. For theclubs
microservice.:numbered:
== Getting started with Micronaut and its CLI (25 minutes)
TIP: Change to the ex01
directory to work on this exercise
The Micronaut CLI is the recommended way to create new Micronaut projects. The CLI includes commands for generating specific categories of projects, allowing you to choose between build tools, test frameworks, and even pick the language you wish to use in your application. The CLI also provides commands for generating artifacts such as controllers, client interfaces, and serverless functions.
The create-app
command is the starting point for creating Micronaut applications.
The CLI is based on the concept of profiles. A profile consist of a project
template (or skeleton), optional features, and profile-specific commands. Commands
from a profile typically are specific to the profile application type; for example,
the service
profile (designed for creation of microservice applications) provides
the create-controller
and create-client
commands.
=== Listing profiles (3 minutes)
icon:hand-o-right[] You can list the available profiles with the list-profiles
command:
Applications generated from a profile can be personalised with features. A feature further customises the newly created project by adding additional dependencies to the build, more files to the project skeleton, etc.
=== Getting information about a profile (2 minutes)
icon:hand-o-right[] To see all the features of a profile, you can
use the profile-info
command:
The service profile
create-bean Creates a singleton bean create-client Creates a client interface create-controller Creates a controller and associated test create-job Creates a job with scheduled method create-test Creates a simple test for the project's testing framework create-websocket-client Creates a Websocket client create-websocket-server Creates a Websocket server help Prints help information for a specific command
+++ +++
=== Creating and running a hello galaxy (15 minutes)
As explained avobe, the create-app
command can be used to create new projects.
It accepts some flags:
.Create-App Flags |=== |Flag|Description|Example
|build
|Build tool (one of gradle
, maven
- default is gradle
)
|--build maven
|profile
|Profile to use for the project (default is service
)
|--profile function-aws
|features
|Features to use for the project, comma-separated
|--features security-jwt,mongo-gorm
|inplace
|If present, generates the project in the current directory (project name is optional if this flag is set)
|--inplace
|===
icon:hand-o-right[] Let's create a hello galaxy project:
icon:hand-o-right[] Now, move into the generated hello-galaxy
folder and let's
create a controller:
icon:hand-o-right[] Open the generated HelloController.java
with your favourite
IDE and make it return "Hello Galaxy!":
icon:hand-o-right[] Now, run the application:
$ ./gradlew run
You will see a line similar to the following once the application has started
14:40:01.187 [main] INFO io.micronaut.runtime.Micronaut - Startup completed in 957ms. Server Running: http://localhost:8080
icon:hand-o-right[] Then, on another shell, make a request to your service:
=== Write an automated test (5 minutes)
While testing manually is acceptable in some situations, going forward it is better to have automated tests to exercise our applications. Fortunately, Micronaut makes testing super easy!
Micronaut applications can be tested with any testing framework, because
io.micronaut.context.ApplicationContext
is capable of spinning up embedded
instances quite easily. The CLI adds support for using JUnit, Spock and Spek.
In addition to that, if you are using JUnit 5 or Spock, there is special support that allows to remove most of the boilerplate about starting/stopping server and injecting beans. Check the https://micronaut-projects.github.io/micronaut-test/latest/guide/index.html[Micronaut Test] project for more information.
We will use Gradle to run the tests, however, if you want to run them from your IDE, make sure you enable annotation processors. For example, in Intellij IDEA:
icon:hand-o-right[] Now, change the generated src/test/java/hello/galaxy/HelloControllerTest.java
to look like this:
+++ +++
icon:hand-o-right[] Then, run the tests:
./gradlew test
Once finished, you should see an output similar to:
BUILD SUCCESSFUL in 5s
== Creating the Clubs microservice (70 minutes)
TIP: Change to the ex02
directory to work on this exercise.
icon:hand-o-right[] In this exercise we are creating the clubs
microservice. Start with:
And open it in your IDE.
The hibernate-jpa
feature will bring to the newly created project:
build.gradle
).src/main/resources/application.yml
).icon:hand-o-right[] Check yourself the above files to see how it is configured.
=== JPA layer (15 minutes)
Our model will reside in the clubs.domain
package. We need to configure JPA to search for
entities in this package.
icon:hand-o-right[] Change jpa
section of src/main/resources/application.yml
so that it looks like:
icon:hand-o-right[] Let's define first a Club
entity under
src/main/java/clubs/domain/Club.java
with 2 string attributes:
name
(mandatory) and stadium
(optional).
You need to use JPA annotations on the entity such as @Entity
and @Id
. If you are not familiar with JPA,
check the solution file.
icon:hand-o-right[] Next, define repository named ClubRepository
as an interface with
the following operations:
Now, let's write the implementation using JPA:
+++ +++
icon:hand-o-right[] Now, let's write a test for our implementation:
+++ +++
WARNING: For package-scanning reasons, you need to ensure your test classes are located at
the top of the package hierarchy, so that Micronaut can find annotations in packages underneath.
In this exercise, place your tests in the clubs
package and make sure there are no classes
outside of it.
=== REST API (30 minutes)
Micronaut helps you writing both the client and server sides of a REST API. In this service, we are going to create the following:
ifdef::generate-diagrams[] [plantuml, clubs-diagram, png] .... ClubsApi <> ClubsClient <>
ClubsApi <|-- ClubsClient ClubsApi <|-- ClubController
ClubsApi : @Get("/") List listClubs() ClubsApi : @Get("/{id}") Club show(@NotNull Long id) ClubsApi : @Post("/") Club save(@NotNull String name, String stadium) .... endif::[]
image::clubs-diagram.png[]
icon:hand-o-right[] Create the ClubsApi
interface, annotating its methods with
io.micronaut.http.annotation.Get
or io.micronaut.http.annotation.Post
as described in the diagram.
icon:hand-o-right[] Then, create ClubsClient
by simply extending from ClubsApi
.
Annotate the interface with io.micronaut.http.client.Client("/")
.
icon:hand-o-right[] Finally, implement the controller ClubController
. Annotate
the class with io.micronaut.http.annotation.Controller("/")
, matching the path
specified on ClubsClient
. Use ClubRepository
to implement the actions by declaring
a constructor dependency on it.
WARNING: The controller actions need to be annotated with @Get
/ @Post
again.
icon:hand-o-right[] Finally, configure logback.xml
to see some relevant output
<1> Debug level for our code <2> This allows to see the HTTP request and responses from the HTTP clients.
icon:hand-o-right[] Once you have it, write an end-to-end test:
+++ +++
=== Load some data for production (15 minutes)
During our tests, we have been seeding test data on demand, as it is a good practise to isolate test data from test to test. However, for production, we want some data loaded
icon:hand-o-right[] Let's create a bean to load some data. Run:
mn create-bean dataLoader
icon:hand-o-right[] Change it to look like:
+++ +++
icon:hand-o-right[] Now, run the application:
./gradlew run
icon:hand-o-right[] And make a request to 0:8080/
to see the results:
=== Register the service in Consul (10 minutes)
We want the clubs
microservice to be discoverable by the fixtures
service.
So we will enable Micronaut's Consul support for service discovery.
icon:hand-o-right[] First, add the neccessary dependency in build.gradle
:
icon:hand-o-right[] Then, change src/main/resources/application.yml
to define
the Consul configuration:
icon:hand-o-right[] Finally, run a Consul instance with Docker:
$ docker run -d --name=dev-consul -e CONSUL_BIND_INTERFACE=eth0 -e CONSUL_UI_BETA=true -p 8500:8500 consul
icon:hand-o-right[] Now, if you run the application, you will see it registers with Consul at startup:
icon:hand-o-right[] If you go the http://localhost:8500/[Consul UI], you can see it shows as registered:
image::consul.png[]
icon:hand-o-right[] You can run yet another instance of clubs
on a different
shell, and see it registered. In order to do that, you need to tell Micronaut to
run on a random port. In application.yml
:
We will use them both with Micronaut's load-balanced HTTP client in the next exercise.
== Creating the Fixtures microservice (70 minutes)
TIP: Change to the ex03
directory to work on this exercise.
icon:hand-o-right[] In this exercise we are creating the fixtures
microservice:
mn create-app --features=mongo-reactive,discovery-consul fixtures
Once again, follow the steps of exercise 1 to add Micronaut Test to this project. Also,
remove the de.flapdoodle.embed.mongo
dependency, as we are using a Dockerized MongoDB
instance.
=== Data layer (35 minutes)
icon:hand-o-right[] First of all, run MongoDB with Docker:
$ docker run -d --name=dev-mongo -p 27017:27017 mongo
icon:hand-o-right[] Then, create the Fixture
domain class with the following properties:
As you can see, we are only storing club's ids. When rendering fixture details,
we will use Micronaut's HTTP client to fetch details from the clubs
microservice.
icon:hand-o-right[] We also need a constructor with annotations that allow Fixture
instances to be marshalled and unmarshalled
to/from JSON and as a MongoDB document:
Be sure to add all the getter and setters as well.
icon:hand-o-right[] The next thing we need is an HTTP client for the clubs
microservice. Create one with:
$ mn create-client clubs
Before actually mapping any endpoint, we are going to create the following hierarchy:
ifdef::generate-diagrams[] [plantuml, clients-diagram, png] .... ClubsApi <> ClubsClient <>
ClubsApi <|-- ClubsClient ClubsApi <|-- ClubsClientMock
ClubsApi : @Get("/{id}") Club show(Long id) .... endif::[]
image::clients-diagram.png[]
ClubsApi
is the interface that contains the client endpoint mappings.ClubsClient
is the production client, is annotated with @Client
and simplyClubsApi
.ClubsClientMock
is a mocking client (resides within src/test/java
), is annotated@Fallback
, and implements ClubsApi
by returning hardcoded instances.This is how ClubsApi
looks like:
We are using a reactive type in the HTTP client response, so that is a hint for Micronaut to make it non-blocking.
Then, the production client:
<1> "clubs"
is the Consul name for the Clubs microservice (which registers
itself with the micronaut.application.name
property).
Finally, the mocking client:
icon:hand-o-right[] We also need a Club
POJO to capture the JSON response from clubs
. Define
it with 2 string fields: name
and stadium
, and its constructor, getters, etc.
icon:hand-o-right[] Now let's create a repository for Fixture
. Following the same convention as
in the previous exercise, begin with an interface:
icon:hand-o-right[] Then, the implementation:
+++ +++
icon:hand-o-right[] And a test:
+++ +++
Make sure it passes.
=== REST API (35 minutes)
icon:hand-o-right[] Let's create a controller for displaying fixtures:
$ mn create-controller fixture
As it was said earlier, our Fixture
class doesn't store club names, but their id's (with the intention of
having this microservice call the other). Therefore, we need a DTO class to represent what our JSON response
is going to look like.
icon:hand-o-right[] Create a POJO named FixtureResponse
with the following attributes:
Now we need a service that transforms a Fixture
into a FixtureResponse
. To do so, it need to make
2 HTTP calls to the clubs
microservice, to get the name of each clubs. It will use ClubsClient
for that.
icon:hand-o-right[] Create a FixtureService
like this:
+++ +++
icon:hand-o-right[] And write a test for it:
+++ +++
Finally, we need the REST controller that connect the dots.
icon:hand-o-right[] Create a FixtureController
that uses FixtureRepository
and FixtureService
as
collaborators to produce a Flowable<FixtureResponse>
response:
=== Load some data and run the application (10 minutes)
icon:hand-o-right[] Similarly to the previous exercise, seed the application with some data.
Also, we need to set the micronaut.server.port
configuration property a value other than 8080,
otherwise, we won't be able to run both services.
icon:hand-o-right[] In application.yml
, set micronaut.server.port
to 8081
icon:hand-o-right[] Now, run the application:
./gradlew run
If you make a request to the default controller, and the clubs
microservice is not running,
you will see an error:
icon:hand-o-right[] Now, run the clubs
service on a different terminal, and try the request again.