Skip to content

Latest commit

 

History

History
729 lines (542 loc) · 25.4 KB

README.adoc

File metadata and controls

729 lines (542 loc) · 25.4 KB
Fork me on GitHub

Introductory workshop about Micronaut.

Software Requirements

In order to do this workshop, you need the following:

  • Linux or MacOS with shell access, and the following installed:

    • curl.

    • wget.

    • unzip.

    • git.

  • JDK 8.

  • Docker. Please pull the following images before attending the workshop:

    • consul.

    • mongo.

Micronaut CLI

  1. Install SDKMAN! if you haven’t done so already.

  2. Install Micronaut CLI:

    $ sdk install micronaut
  3. 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:

git clone https://github.com/alvarosanchez/micronaut-workshop-java.git
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: [hand o right]

Application architecture

Throughout this workshop, we will be creating a football (soccer) management system.

football diagram
  • clubs is the microservice responsible for managing clubs. It uses Hibernate as a data access layer.

  • fixtures manages all game fixtures, storing its data in MongoDB. For the teams playing in a game, it doesn’t store their full details, but rather their ID. It has a service-discovery-enabled HTTP client to fetch club details from the clubs microservice.

1. 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.

1.1. Listing profiles (3 minutes)

[hand o right] You can list the available profiles with the list-profiles command:

$ mn list-profiles
| Available Profiles
--------------------
  cli                 The cli profile
  configuration       The profile for creating the configuration
  federation          The federation profile
  function-aws        The function profile for AWS Lambda
  function-aws-alexa  The function profile for AWS Lambda
  grpc                Profile for Creating GRPC Services
  kafka               The Kafka messaging profile
  profile             A profile for creating new Micronaut profiles
  rabbitmq            The RabbitMQ messaging profile
  service             The service profile

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.

1.2. Getting information about a profile (2 minutes)

[hand o right] To see all the features of a profile, you can use the profile-info command:

Click to expand

$ mn profile-info service
| Profile: service
--------------------
The service profile

| Provided Commands:
--------------------
  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

| Provided Features:
--------------------
  annotation-api        Adds Java annotation API
  application           Facilitates creating an executable JVM application and adds support for creating fat/uber JARs
  aws-api-gateway       Adds support for AWS API Gateway
  aws-api-gateway-graal  Creates an AWS API Gateway Proxy Lambda with Graal Native Image
  cassandra             Adds support for Cassandra in the application
  config-consul         Adds support for Distributed Configuration with Consul (https://www.consul.io)
  discovery-consul      Adds support for Service Discovery with Consul (https://www.consul.io)
  discovery-eureka      Adds support for Service Discovery with Eureka
  elasticsearch         Adds support for Elasticsearch in the application
  file-watch            Adds automatic restarts and file watch
  flyway                Adds support for Flyway database migrations (https://flywaydb.org/)
  graal-native-image    Allows Building a Native Image
  graphql               Adds support for GraphQL in the application
  groovy                Creates a Groovy application
  hibernate-gorm        Adds support for GORM persistence framework
  hibernate-jpa         Adds support for Hibernate/JPA
  http-client           Adds support for creating HTTP clients
  http-server           Adds support for running a Netty server
  java                  Creates a Java application
  jdbc-dbcp             Configures SQL DataSource instances using Commons DBCP
  jdbc-hikari           Configures SQL DataSource instances using Hikari Connection Pool
  jdbc-tomcat           Configures SQL DataSource instances using Tomcat Connection Pool
  jib                   Adds support for Jib builds
  jrebel                Adds support for class reloading with JRebel (requires separate JRebel installation)
  junit                 Adds support for the JUnit 5 testing framework
  kafka                 Adds support for Kafka
  kafka-streams         Adds support for Kafka Streams
  kotlin                Creates a Kotlin application
  liquibase             Adds support for Liquibase database migrations (http://www.liquibase.org/)
  logback               Adds Logback Logging
  management            Adds support for management endpoints
  micrometer            Adds support for Micrometer metrics
  micrometer-atlas      Adds support for Micrometer metrics (w/ Atlas reporter)
  micrometer-cloudwatch  Adds support for Micrometer metrics (w/ AWS Cloudwatch reporter)
  micrometer-graphite   Adds support for Micrometer metrics (w/ Graphite reporter)
  micrometer-prometheus  Adds support for Micrometer metrics (w/ Prometheus reporter)
  micrometer-statsd     Adds support for Micrometer metrics (w/ Statsd reporter)
  mongo-gorm            Configures GORM for MongoDB for Groovy applications
  mongo-reactive        Adds support for the Mongo Reactive Streams Driver
  neo4j-bolt            Adds support for the Neo4j Bolt Driver
  neo4j-gorm            Configures GORM for Neo4j for Groovy applications
  netflix-archaius      Adds support for Netflix Archaius in the application
  netflix-hystrix       Adds support for Netflix Hystrix in the application
  netflix-ribbon        Adds support for Netflix Ribbon in the application
  picocli               Adds support for command line parsing (http://picocli.info)
  postgres-reactive     Adds support for the Reactive Postgres driver in the application
  rabbitmq              Adds support for RabbitMQ in the application
  redis-lettuce         Configures the Lettuce driver for Redis
  security-jwt          Adds support for JWT (JSON Web Token) based Authentication
  security-session      Adds support for Session based Authentication
  spek                  Adds support for the Spek testing framework
  spock                 Adds support for the Spock testing framework
  springloaded          Adds support for class reloading with Spring-Loaded
  swagger-groovy        Configures Swagger (OpenAPI) Integration for Groovy
  swagger-java          Configures Swagger (OpenAPI) Integration for Java
  swagger-kotlin        Configures Swagger (OpenAPI) Integration for Kotlin
  tracing-jaeger        Adds support for distributed tracing with Jaeger (https://www.jaegertracing.io)
  tracing-zipkin        Adds support for distributed tracing with Zipkin (https://zipkin.io)

1.3. 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:

Table 1. 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

[hand o right] Let’s create a hello galaxy project:

$ mn create-app hello-galaxy
| Generating Java project...
| Application created at /Users/alvarosanchez/hello-galaxy

[hand o right] Now, move into the generated hello-galaxy folder and let’s create a controller:

$ mn create-controller hello
| Rendered template Controller.java to destination src/main/java/hello/galaxy/HelloController.java
| Rendered template ControllerTest.java to destination src/test/java/hello/galaxy/HelloControllerTest.java

[hand o right] Open the generated HelloController.java with your favourite IDE and make it return "Hello Galaxy!":

link:./ex01/solution/hello-galaxy/src/main/java/hello/galaxy/HelloController.java[role=include]

[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

[hand o right] Then, on another shell, make a request to your service:

$ curl 0:8080/hello
Hello Galaxy!

1.4. 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 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:

annotationprocessorsintellij

[hand o right] Now, change the generated src/test/java/hello/galaxy/HelloControllerTest.java to look like this:

Click to expand

link:./ex01/solution/hello-galaxy/src/test/java/hello/galaxy/HelloControllerTest.java[role=include]

[hand o right] Then, run the tests:

./gradlew test

Once finished, you should see an output similar to:

BUILD SUCCESSFUL in 5s

2. Creating the Clubs microservice (70 minutes)

Tip
Change to the ex02 directory to work on this exercise.

[hand o right] In this exercise we are creating the clubs microservice. Start with:

mn create-app --features hibernate-jpa clubs

And open it in your IDE.

The hibernate-jpa feature will bring to the newly created project:

  • The required build dependencies to have Hibernate, a Tomcat-based JDBC connection pool and an H2 in-memory database (build.gradle).

  • The data source configuration to use such H2 database (src/main/resources/application.yml).

[hand o right] Check yourself the above files to see how it is configured.

2.1. 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.

[hand o right] Change jpa section of src/main/resources/application.yml so that it looks like:

link:./ex02/solution/clubs/src/main/resources/application.yml[role=include]

[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.

[hand o right] Next, define repository named ClubRepository as an interface with the following operations:

link:./ex02/solution/clubs/src/main/java/clubs/domain/ClubRepository.java[role=include]

Now, let’s write the implementation using JPA:

Click to expand

link:./ex02/solution/clubs/src/main/java/clubs/domain/ClubRepositoryImpl.java[role=include]

[hand o right] Now, let’s write a test for our implementation:

Click to expand

link:./ex02/solution/clubs/src/test/java/clubs/ClubRepositoryImplTest.java[role=include]

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.

2.2. 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:

clubs diagram

[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.

[hand o right] Then, create ClubsClient by simply extending from ClubsApi. Annotate the interface with io.micronaut.http.client.Client("/").

[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.

[hand o right] Finally, configure logback.xml to see some relevant output

link:./ex02/solution/clubs/src/main/resources/logback.xml[role=include]
  1. Debug level for our code

  2. This allows to see the HTTP request and responses from the HTTP clients.

[hand o right] Once you have it, write an end-to-end test:

Click to expand

link:./ex02/solution/clubs/src/test/java/clubs/ClubControllerTest.java[role=include]

2.3. 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

[hand o right] Let’s create a bean to load some data. Run:

mn create-bean dataLoader

[hand o right] Change it to look like:

Click to expand

link:./ex02/solution/clubs/src/main/java/clubs/init/DataLoader.java[role=include]

[hand o right] Now, run the application:

./gradlew run

[hand o right] And make a request to 0:8080/ to see the results:

2.4. 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.

[hand o right] First, add the neccessary dependency in build.gradle:

link:./ex02/solution/clubs/build.gradle[role=include]

[hand o right] Then, change src/main/resources/application.yml to define the Consul configuration:

link:./ex02/solution/clubs/src/main/resources/application.yml[role=include]

[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

[hand o right] Now, if you run the application, you will see it registers with Consul at startup:

$ ./gradlew run
...
04:20:09.501 [nioEventLoopGroup-1-3] INFO  i.m.d.registration.AutoRegistration - Registered service [clubs] with Consul
...

[hand o right] If you go the Consul UI, you can see it shows as registered:

consul

[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:

link:./ex02/solution/clubs/src/main/resources/application.yml[role=include]

We will use them both with Micronaut’s load-balanced HTTP client in the next exercise.

3. Creating the Fixtures microservice (70 minutes)

Tip
Change to the ex03 directory to work on this exercise.

[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.

3.1. Data layer (35 minutes)

[hand o right] First of all, run MongoDB with Docker:

$ docker run -d --name=dev-mongo -p 27017:27017 mongo

[hand o right] Then, create the Fixture domain class with the following properties:

link:./ex03/solution/fixtures/src/main/java/fixtures/domain/Fixture.java[role=include]

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.

[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:

link:./ex03/solution/fixtures/src/main/java/fixtures/domain/Fixture.java[role=include]

Be sure to add all the getter and setters as well.

[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:

clients diagram
  • ClubsApi is the interface that contains the client endpoint mappings.

  • ClubsClient is the production client, is annotated with @Client and simply extends from ClubsApi.

  • ClubsClientMock is a mocking client (resides within src/test/java), is annotated with @Fallback, and implements ClubsApi by returning hardcoded instances.

This is how ClubsApi looks like:

link:./ex03/solution/fixtures/src/main/java/fixtures/clubs/ClubsApi.java[role=include]

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:

link:./ex03/solution/fixtures/src/main/java/fixtures/clubs/ClubsClient.java[role=include]
  1. "clubs" is the Consul name for the Clubs microservice (which registers itself with the micronaut.application.name property).

Finally, the mocking client:

link:./ex03/solution/fixtures/src/test/java/fixtures/ClubsClientMock.java[role=include]

[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.

[hand o right] Now let’s create a repository for Fixture. Following the same convention as in the previous exercise, begin with an interface:

link:./ex03/solution/fixtures/src/main/java/fixtures/domain/FixtureRepository.java[role=include]

[hand o right] Then, the implementation:

Click to expand

link:./ex03/solution/fixtures/src/main/java/fixtures/domain/FixtureRepositoryImpl.java[role=include]

[hand o right] And a test:

Click to expand

link:./ex03/solution/fixtures/src/test/java/fixtures/FixtureRepositoryImplTest.java[role=include]

Make sure it passes.

3.2. REST API (35 minutes)

[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.

[hand o right] Create a POJO named FixtureResponse with the following attributes:

link:./ex03/solution/fixtures/src/main/java/fixtures/controller/FixtureResponse.java[role=include]

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.

[hand o right] Create a FixtureService like this:

Click to expand

link:./ex03/solution/fixtures/src/main/java/fixtures/service/FixtureService.java[role=include]

[hand o right] And write a test for it:

Click to expand

link:./ex03/solution/fixtures/src/test/java/fixtures/FixtureServiceTest.java[role=include]

Finally, we need the REST controller that connect the dots.

[hand o right] Create a FixtureController that uses FixtureRepository and FixtureService as collaborators to produce a Flowable<FixtureResponse> response:

link:./ex03/solution/fixtures/src/main/java/fixtures/controller/FixtureController.java[role=include]

3.3. Load some data and run the application (10 minutes)

[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.

[hand o right] In application.yml, set micronaut.server.port to 8081

[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:

{"message":"Internal Server Error: No available services for ID: clubs"}

[hand o right] Now, run the clubs service on a different terminal, and try the request again.