Microservices: Upgrading to Micronaut 2.0
It is amazing how quickly time passed. It seems that it was yesterday when we started our journey with Micronaut, but we are just about to celebrate the 2nd anniversary of the first commit in our LAB Insurance Sales Portal demo project. Our project demonstrates how you can take advantage of Micronaut’s cool features to build and connect microservices. In this post, we will present our experience with upgrading the project to a just-released version of Micronaut – Micronaut 2.0. We will also discuss what’s new and hot in this version.
But first, some reminders about the functionality and purpose of our project. Insurance Sales Portal is a system that allows insurance agents to log in, offer various insurance products from a catalog, calculate prices, create offers, sell policies, print required documents, search and review sold policies, view various sales statistics, and chat with other agents.
The system uses various data sources: relational databases (H2), document databases (MongoDb), and search engines (Elastic Search). It uses Apache Kafka to connect microservices using event-driven architecture. It also demonstrates how to uses service discovery, distributed tracing, and how to implement HTTP REST-based communication between services using Micronaut’s declarative HttpClient.
If you would like to read a more detailed description, feel free to click – Building Microservices with Micronaut.
Upgrading first microservices
Let’s start upgrading. Our first target is Product Service. This service uses MongoDb and exposed the product catalog via the REST API.
I am not a big fan of messing in the pom.xml files, especially when it comes to major version upgrades. But Micronaut version 2.0 brings a very cool feature that will save me a lot of time. I can now go to micronaut.io/launch and use the user interface to select dependencies, build tool, unit test frameworks, java version, and stuff.
Now I can click the Preview button, see my newly generated project structure, select pom.xml, examine it, and use it as the knowledge source to update pom files in my project.
Micronaut’s Maven support has been greatly enhanced in this version. The first thing we notice is a new parent pom. We are going to use it and add it as a parent project in our pom.xml.
<parent> <groupId>io.micronaut</groupId> <artifactId>micronaut-parent</artifactId> <version>2.0.0</version> </parent>
We now update the properties section and change Micronaut’s version number:
<properties> <micronaut.version>2.0.0</micronaut.version> <jdk.version>1.8</jdk.version> <maven.compiler.target>${jdk.version}</maven.compiler.target> <maven.compiler.source>${jdk.version}</maven.compiler.source> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <exec.mainClass>pl.altkom.asc.lab.micronaut.poc.product.service.ProductApplication</exec.mainClass> </properties>
We can get rid of referenced Micronaut’s BOM, which is no longer needed. Now it’s time to look at the dependencies list. It seems that some packages in Micronaut changed Group IDs. One of them is micronaut-mongo-reactive, which was moved from io.micronaut.configuration to io.micronaut.mongodb.
We will also use our upgrade to Micronaut version 2 as an opportunity to upgrade test framework in all our projects to JUnit 5.
In order to do that we replaced all test dependencies previously used with a new list:
<!-- TEST --> <dependency> <groupId>org.junit.jupiter</groupId> <artifactId>junit-jupiter-api</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>org.junit.jupiter</groupId> <artifactId>junit-jupiter-engine</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>io.micronaut.test</groupId> <artifactId>micronaut-test-junit5</artifactId> <scope>test</scope> </dependency> <!-- TEST -->
It seems that we are done with the dependencies list. Now we are going to replace the build section with the one from freshly generated pom for the new 2.0 project. After that, we need to add Lombok to the list of annotation processor paths. It is important to put it before Micronaut processors, so that all constructors, getters/setters generated by Lombok will be available to Micronaut’s bean analyzers.
<build> <plugins> <plugin> <groupId>io.micronaut.build</groupId> <artifactId>micronaut-maven-plugin</artifactId> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-shade-plugin</artifactId> </plugin> </plugins> <pluginManagement> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.7.0</version> <configuration> <source>${jdk.version}</source> <target>${jdk.version}</target> <compilerArgs> <arg>-parameters</arg> </compilerArgs> <annotationProcessorPaths> <path> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.0</version> </path> <path> <groupId>io.micronaut</groupId> <artifactId>micronaut-inject-java</artifactId> <version>${micronaut.version}</version> </path> <path> <groupId>io.micronaut</groupId> <artifactId>micronaut-validation</artifactId> <version>${micronaut.version}</version> </path> </annotationProcessorPaths> </configuration> </plugin> </plugins> </pluginManagement> </build>
With this change, we are ready to build, test, and run our first Micronaut 2.0 based microservices. Micronaut team claims that this new version improves already great startup performance. Let’s check this out.
Product service | Micronaut 1.3 | Micronaut 2.0 |
Startup time | 1651 ms | 1194 ms |
The last thing to add here is a new Micronaut Maven’s plugin. With it, we can:
mvn mn:run
This command will build and run our project. It will also watch for changes in our source code, and when they are detected, it will rebuild and restart the application, which combined with incredible startup times gives us fantastic developers’ experience. We no longer how to stop, build, and start our app.
Upgrading service with JPA and transactions
Let’s tackle a slightly more complex task. The next service we will upgrade is Pricing Service. It is responsible for an insurance price calculation. It uses JPA (Hibernate), Spring transactions, and Micronaut Data access toolkit.
With Micronaut 2 we no longer have to use spring managed transactions. In fact, it is recommended not to use it and instead use Micronaut-based transaction management. To achieve this instead of using io.micronaut.spring.tx.annotation.Transactional we use JEE javax.transaction.Transactional. If you need a read-only transaction, you have to use Micronaut @Readonly annotation instead of Spring’s @Transactional(readonly=true).
@ReadOnly public CalculatePriceResult handle(CalculatePriceCommand calculatePriceCommand) { Tariff tariff = tariffs.getByCode(calculatePriceCommand.getProductCode()); Calculation calculation = tariff.calculatePrice(toCalculation(calculatePriceCommand)); return resultFromCalculation(calculation); }
This means that we can get rid of all Spring related dependencies from our pom.xml file.
<!-- spring managed tx --> <dependency> <groupId>io.micronaut</groupId> <artifactId>micronaut-spring</artifactId> </dependency> <dependency> <groupId>io.micronaut.data</groupId> <artifactId>micronaut-data-spring</artifactId> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-orm</artifactId> <version>5.2.0.RELEASE</version> </dependency>
We also have to update GroupId for micronaut-jdbc-hikari, which is now io.micronaut.sql.
Of course, we have to repeat changes common to all projects that we are going to upgrade: add parent Micronaut project, remove BOM reference, update properties, and build sections.
There is one last thing we should. Official Micronaut’s documentation tells us that since 2.0 we should mark blocking code with @ExecuteOn annotation specifying a thread pool that our blocking code should be executed on. If we forget to do this, no warning or exception will be thrown but you can expect performance degradation if threads in the request processing pool will be blocked.
As our service uses JPA which is blocking communication, we most definitely should modify our controller to tell specify thread pool to use for blocking communications.
@ExecuteOn(TaskExecutors.IO) @Override public CalculatePriceResult calculatePrice(CalculatePriceCommand cmd) { return calculatePriceHandler.handle(cmd); } }
With that change, we are good to go. Let’s build and run our service.
Pricing service | Micronaut 1.3 | Micronaut 2.0 |
Startup time | 3626 ms | 2905ms |
Upgrading the rest of the pack
We continue by modifying pom files for all other services. We apply the same schema – add parent pom, remove BOM reference, update properties, update build section (not forgetting to handle Lombok case).
In some services, we have to adjust GroupIds for packages that were moved. For example Kafka dependency changed from io.micronaut.configuration:micronaut-kafka to io.micronaut.kafka:micronaut-kafka fo Policy Service and Policy Search Service.
Also, Netflix Hystrix dependency has been moved. We had to update Payment Service pom file.
BEFORE: <dependency> <groupId>io.micronaut.configuration</groupId> <artifactId>micronaut-netflix-hystrix</artifactId> <scope>compile</scope> </dependency> AFTER: <dependency> <groupId>io.micronaut.netflix</groupId> <artifactId>micronaut-netflix-hystrix</artifactId> <scope>compile</scope> </dependency>
With this work done, we can check how startup times changed for these services.
Startup time: | Micronaut 1.3 | Micronaut 2.0 |
Policy service | 3088ms | 2246ms |
Policy Search service | 1802ms | 1413ms |
Payment service | 3655ms | 2730ms |
Upgrading security service
There are some breaking changes in Micronaut Security 2.0. Let’s see what we had to do, to upgrade our Auth Service which is responsible for handling authentication and producing JWT tokens with user claims.
The first thing we needed it to adjust micronaut-jdbc-hikari and micronaut-security-jwt groupIds.
BEFORE: <dependency> <groupId>io.micronaut.configuration</groupId> <artifactId>micronaut-jdbc-hikari</artifactId> <scope>runtime</scope> </dependency> AFTER: <dependency> <groupId>io.micronaut.sql</groupId> <artifactId>micronaut-jdbc-hikari</artifactId> <scope>runtime</scope> </dependency>
BEFORE: <dependency> <groupId>io.micronaut</groupId> <artifactId>micronaut-security-jwt</artifactId> <scope>compile</scope> </dependency> AFTER: <dependency> <groupId>io.micronaut.security</groupId> <artifactId>micronaut-security-jwt</artifactId> <scope>compile</scope> </dependency>
The main Interface for authentication implementation – AuthenticationProvider – has changed. Actually, its only method authenticate parameters have changed.
In the previous version this method looked like this:
Publisher<AuthenticationResponse> authenticate(AuthenticationRequest request)
And was changed into:
Publisher<AuthenticationResponse> authenticate(HttpRequest<?> httpRequest, AuthenticationRequest<?, ?> authenticationRequest)
Username and password are now provided through authenticationRequest parameter. So we need to change our code a little:
Optional<InsuranceAgent> agent = insuranceAgents.findByLogin((String)authenticationRequest.getIdentity()); if (agent.isPresent() && agent.get().passwordMatches((String) authenticationRequest.getSecret())) { return Flowable.just(createUserDetails(agent.get())); }
We also have to update application.yaml because in 2.0 security module uses different configuration properties.
We have to replace old configuration:
security: enabled: true endpoints: login: enabled: true oauth: enabled: true token: jwt: enabled: true generator: access-token-expiration: 86400 signatures: secret: generator: secret: pleaseChangeThisSecretForANewOne
With a new one:
security: authentication: bearer token: jwt: generator: access-token: expiration: 86400 signatures: secret: generator: secret: pleaseChangeThisSecretForANewOne
Refresh tokens are by default not generated. We have to adjust our test to expect null values in refresh tokens.
Now we are ready to build and run our Auth service.
Auth Service | Micronaut 1.3 | Micronaut 2.0 |
Startup time | 1645ms | 1306ms |
The last step is to upgrade our API gateway. This task is pretty simple as only GroupIds for micronaut-security-jwt.
That’s it my friends. We upgraded our whole solution to Micronaut 2.0!
Micronaut 2.0 New Features Summary
Let’s quickly summarize what is new in the Micronaut 2.0 version:
- Improved Maven support: we have a new parent project, which simplifies configuration, and we have a brand-new Maven plugin. With the plugin, we can run our apps and the plugin will detect changes, rebuild and restart the app instantly. This greatly improves developers’ experience as manual rebuilds and restarts are no longer needed.
- Micronaut Native Transactions: we can finally get rid of any Spring dependencies as Micronaut native transactions are now available.
- Even Better Startup Performance: it is hard to believe that Micronaut’s team managed to beat it’s previous already super-fast startups by almost 20% in most of the tested cases.
- Better Introspection: we can now introspect interfaces with private implementations.
- Support for JDK 14: we can target Java 14, which is our next upgrade step for this project.
- Micronaut Launch: we can now generate a new project using web app or REST API, without need to install CLI tool.
- Manual thread pool selection: we now have to manually specify thread pools for our blocking code.
- HTTP/2 support.
- Support for servlet based environments: Micronaut can now be used to build apps in servlet based environments like Tomcat, Jetty, or Undertow.
- Support for XML in request/response processing: Micronaut will now correctly accept and process HTTP Accept header. This is something we really missed in some projects where customers required XML-based communication between services.
- Dependencies upgrades.
And many other small changes that are listed here.
Links
- ASC LAB Insurance Sales Portal
- Building microservices with Micronaut
- Micronaut Launch
- Micronaut Docs Upgrading Section
- Micronaut Maven plugin
Wojciech Suwała
Head Architect