Kotlin Microservices with Micronaut, Spring Cloud and JPA
Micronaut Framework provides support for Kotlin built upon Kapt compiler plugin. It also implements the most popular cloud-native patterns like distributed configuration, service discovery, and client-side load balancing. These features allow you to include your application built on top of Micronaut into the existing Kotlin microservices-based system.
The most popular example of such an approach may be an integration with Spring Cloud ecosystem. If you have already used Spring Cloud, it is very likely you built your microservices-based architecture using Eureka discovery server and Spring Cloud Config as a configuration server. Beginning from version 1.1 Micronaut supports both these popular tools being a part of Spring Cloud project. That’s good news, because in version 1.0 the only supported distributed solution was Consul, and there were no possibility to use Eureka discovery together with Consul property source (running them together ends with exception).
In this article you will learn how to:
- Configure Micronaut Maven support for Kotlin using Kapt compiler
- Implement microservices with Micronaut and Kotlin
- Integrate Micronaut with Spring Cloud Eureka discovery server
- Integrate Micronaut with Spring Cloud Config server
- Configure JPA/Hibernate support for application built on top Micronaut
- For simplification we run a single instance of PostgreSQL shared between all sample microservices
Our architecture is pretty similar to the architecture described in my previous article about Micronaut Quick Guide to Microservice with Micronaut Framework. We also have three microservice that communicate to each other. We use Spring Cloud Eureka and Spring Cloud Config for discovery and distributed configuration instead of Consul. Every service has a backend store – PostgreSQL database. This architecture has been visualized in the following picture.
After that short introduction we may proceed to the development. Let’s begin from configuring Kotlin support for Micronaut.
1. Kotlin with Micronaut microservices – configuration
Support for Kotlin with Kapt compiler plugin is described well on Micronaut docs site (https://docs.micronaut.io/1.1.0.M1/guide/index.html#kotlin). However, I decided to use Maven instead of Gradle, so our configuration will be slightly different than instructions for Gradle. We configure Kapt inside the Maven plugin for Kotlin kotlin-maven-plugin
. Thanks to that Kapt will create Java “stub” classes for each of your Kotlin classes, which can then be processed by Micronaut’s Java annotation processor. The Micronaut annotation processors are declared inside tag annotationProcessorPaths
in the configuration section. Here’s the full Maven configuration to provide support for Kotlin. Besides core library micronaut-inject-java
, we also use annotations from tracing, openapi and JPA libraries.
<plugin>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-maven-plugin</artifactId>
<dependencies>
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-maven-allopen</artifactId>
<version>${kotlin.version}</version>
</dependency>
</dependencies>
<configuration>
<jvmTarget>1.8</jvmTarget>
</configuration>
<executions>
<execution>
<id>compile</id>
<phase>compile</phase>
<goals>
<goal>compile</goal>
</goals>
</execution>
<execution>
<id>test-compile</id>
<phase>test-compile</phase>
<goals>
<goal>test-compile</goal>
</goals>
</execution>
<execution>
<id>kapt</id>
<goals>
<goal>kapt</goal>
</goals>
<configuration>
<sourceDirs>
<sourceDir>src/main/kotlin</sourceDir>
</sourceDirs>
<annotationProcessorPaths>
<annotationProcessorPath>
<groupId>io.micronaut</groupId>
<artifactId>micronaut-inject-java</artifactId>
<version>${micronaut.version}</version>
</annotationProcessorPath>
<annotationProcessorPath>
<groupId>io.micronaut.configuration</groupId>
<artifactId>micronaut-openapi</artifactId>
<version>${micronaut.version}</version>
</annotationProcessorPath>
<annotationProcessorPath>
<groupId>io.micronaut</groupId>
<artifactId>micronaut-tracing</artifactId>
<version>${micronaut.version}</version>
</annotationProcessorPath>
<annotationProcessorPath>
<groupId>javax.persistence</groupId>
<artifactId>javax.persistence-api</artifactId>
<version>2.2</version>
</annotationProcessorPath>
<annotationProcessorPath>
<groupId>io.micronaut.configuration</groupId>
<artifactId>micronaut-hibernate-jpa</artifactId>
<version>1.1.0.RC2</version>
</annotationProcessorPath>
</annotationProcessorPaths>
</configuration>
</execution>
</executions>
</plugin>
We also should not run maven-compiler-plugin
during the compilation phase. Kapt compiler generates Java classes, so we don’t need to run any other compilator during the build.
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<proc>none</proc>
<source>1.8</source>
<target>1.8</target>
</configuration>
<executions>
<execution>
<id>default-compile</id>
<phase>none</phase>
</execution>
<execution>
<id>default-testCompile</id>
<phase>none</phase>
</execution>
<execution>
<id>java-compile</id>
<phase>compile</phase>
<goals>
<goal>compile</goal>
</goals>
</execution>
<execution>
<id>java-test-compile</id>
<phase>test-compile</phase>
<goals>
<goal>testCompile</goal>
</goals>
</execution>
</executions>
</plugin>
Finally, we will add Kotlin core library and Jackson module for JSON serialization.
<dependency>
<groupId>com.fasterxml.jackson.module</groupId>
<artifactId>jackson-module-kotlin</artifactId>
</dependency>
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-stdlib-jdk8</artifactId>
<version>${kotlin.version}</version>
</dependency>
If you are running the application with Intellij you should first enable annotation processing. To do that go to Build, Execution, Deployment -> Compiler -> Annotation Processors as shown below.
2. Running Postgres
Before proceeding to the development we have to start an instance of PostgreSQL database. It will be started as a Docker container. For me, PostgreSQL is now available under address 192.168.99.100:5432
, because I’m using Docker Toolbox.
$ docker run -d --name postgres -e POSTGRES_USER=micronaut -e POSTGRES_PASSWORD=123456 -e POSTGRES_DB=micronaut -p 5432:5432 postgres
3. Enabling Hibernate for Micronaut
Hibernate configuration is a little harder for Micronaut than for Spring Boot. We don’t have any projects like Spring Data JPA, where almost all are auto-configured. Besides a specific JDBC driver for integration with the database, we have to include the following dependencies. We may choose between three available libraries providing datasource implementation: Tomcat, Hikari or DBCP.
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<version>42.2.5</version>
</dependency>
<dependency>
<groupId>io.micronaut.configuration</groupId>
<artifactId>micronaut-jdbc-hikari</artifactId>
</dependency>
<dependency>
<groupId>io.micronaut.configuration</groupId>
<artifactId>micronaut-hibernate-jpa</artifactId>
</dependency>
<dependency>
<groupId>io.micronaut.configuration</groupId>
<artifactId>micronaut-hibernate-validator</artifactId>
</dependency>
The next step is to provide some configuration settings. All the properties will be stored on the configuration server. We have to set database connection settings and credentials. The JPA configuration settings are provided under jpa.*
key. We force Hibernate to update the database on application startup and print all the SQL logs (only for tests).
datasources:
default:
url: jdbc:postgresql://192.168.99.100:5432/micronaut?ssl=false
username: micronaut
password: 123456
driverClassName: org.postgresql.Driver
jpa:
default:
packages-to-scan:
- 'pl.piomin.services.department.model'
properties:
hibernate:
hbm2ddl:
auto: update
show_sql: true
Here’s our sample domain object.
@Entity
data class Department(@Id @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "department_id_seq") @SequenceGenerator(name = "department_id_seq", sequenceName = "department_id_seq") var id: Long,
var organizationId: Long, var name: String) {
@Transient
var employees: MutableList<Employee> = mutableListOf()
}
The repository bean needs to inject EntityManager
using @PersistentContext
and @CurrentSession
annotations. All functions need to be annotated with @Transactional
, what requires the methods not to be final (open
modifier in Kotlin).
@Singleton
open class DepartmentRepository(@param:CurrentSession @field:PersistenceContext val entityManager: EntityManager) {
@Transactional
open fun add(department: Department): Department {
entityManager.persist(department)
return department
}
@Transactional(readOnly = true)
open fun findById(id: Long): Department = entityManager.find(Department::class.java, id)
@Transactional(readOnly = true)
open fun findAll(): List<Department> = entityManager.createQuery("SELECT d FROM Department d").resultList as List<Department>
@Transactional(readOnly = true)
open fun findByOrganization(organizationId: Long) = entityManager.createQuery("SELECT d FROM Department d WHERE d.organizationId = :orgId")
.setParameter("orgId", organizationId)
.resultList as List<Department>
}
4. Running Spring Cloud Config Server
Running Spring Cloud Config server is very simple. I have already described that in some of my previous articles. All those were prepared for Java, while today we start it as a Kotlin application. Here’s our main class. It should be annotated with @EnableConfigServer
.
@SpringBootApplication
@EnableConfigServer
class ConfigApplication
fun main(args: Array<String>) {
runApplication<ConfigApplication>(*args)
}
Besides Kotlin core dependency we need to include artifact spring-cloud-config-server
.
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-config-server</artifactId>
</dependency>
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-stdlib-jdk8</artifactId>
<version>${kotlin.version}</version>
</dependency>
By default, the config server tries to use Git as properties source backend. We prefer using classpath resources, what’s much simpler for our tests. To do that, we have to enable native
profile. We will also set server port to 8888
.
spring:
application:
name: config-service
profiles:
active: native
server:
port: 8888
If you place all configuration under directory /src/main/resources/config
they will be automatically load after start.
Here’s a configuration file for department-service
.
micronaut:
server:
port: -1
router:
static-resources:
swagger:
paths: classpath:META-INF/swagger
mapping: /swagger/**
datasources:
default:
url: jdbc:postgresql://192.168.99.100:5432/micronaut?ssl=false
username: micronaut
password: 123456
driverClassName: org.postgresql.Driver
jpa:
default:
packages-to-scan:
- 'pl.piomin.services.department.model'
properties:
hibernate:
hbm2ddl:
auto: update
show_sql: true
endpoints:
info:
enabled: true
sensitive: false
eureka:
client:
registration:
enabled: true
defaultZone: "localhost:8761"
5. Running Eureka Server
Eureka server will also be run as a Spring Boot application written in Kotlin.
@SpringBootApplication
@EnableEurekaServer
class DiscoveryApplication
fun main(args: Array<String>) {
runApplication<DiscoveryApplication>(*args)
}
We also need to include a single dependency spring-cloud-starter-netflix-eureka-server
besides kotlin-stdlib-jdk8
.
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-stdlib-jdk8</artifactId>
<version>${kotlin.version}</version>
</dependency>
We run standalone instance of Eureka on port 8761.
spring:
application:
name: discovery-service
server:
port: 8761
eureka:
instance:
hostname: localhost
client:
registerWithEureka: false
fetchRegistry: false
serviceUrl:
defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
6. Integrating Micronaut microservices with Spring Cloud
The implementation of a distributed configuration client is automatically included to Micronaut core. We only need to include a module for service discovery.
<dependency>
<groupId>io.micronaut</groupId>
<artifactId>micronaut-discovery-client</artifactId>
</dependency>
We don’t have to place anything in the source code. All the features can be enabled via configuration settings. First, we need to enable a config client by setting property micronaut.config-client.enabled
to true
. The next step is to enable specific implementation of config client – in that case Spring Cloud Config, and then set target url.
micronaut:
application:
name: department-service
config-client:
enabled: true
spring:
cloud:
config:
enabled: true
uri: http://localhost:8888/
Each application fetches properties from the configuration server. The part of configuration responsible for enabling discovery based on the Eureka server is visible below.
eureka:
client:
registration:
enabled: true
defaultZone: "localhost:8761"
7. Running Micronaut Kotlin applications
Kapt needs to be able to compile Kotlin code to Java successfully. That’s why we place the method inside class declaration, and annotate it with @JvmStatic
. The main class visible below is also annotated with @OpenAPIDefinition
in order to generate Swagger definition for API methods.
@OpenAPIDefinition(
info = Info(
title = "Departments Management",
version = "1.0",
description = "Department API",
contact = Contact(url = "https://piotrminkowski.com", name = "Piotr Mińkowski", email = "piotr.minkowski@gmail.com")
)
)
open class DepartmentApplication {
companion object {
@JvmStatic
fun main(args: Array<String>) {
Micronaut.run(DepartmentApplication::class.java)
}
}
}
Here’s the controller class from department-service
. It injects a repository bean for database integration and EmployeeClient
for HTTP communication with employee-service
.
@Controller("/departments")
open class DepartmentController(private val logger: Logger = LoggerFactory.getLogger(DepartmentController::class.java)) {
@Inject
lateinit var repository: DepartmentRepository
@Inject
lateinit var employeeClient: EmployeeClient
@Post
fun add(@Body department: Department): Department {
logger.info("Department add: {}", department)
return repository.add(department)
}
@Get("/{id}")
fun findById(id: Long): Department? {
logger.info("Department find: id={}", id)
return repository.findById(id)
}
@Get
fun findAll(): List<Department> {
logger.info("Department find")
return repository.findAll()
}
@Get("/organization/{organizationId}")
@ContinueSpan
open fun findByOrganization(@SpanTag("organizationId") organizationId: Long): List<Department> {
logger.info("Department find: organizationId={}", organizationId)
return repository.findByOrganization(organizationId)
}
@Get("/organization/{organizationId}/with-employees")
@ContinueSpan
open fun findByOrganizationWithEmployees(@SpanTag("organizationId") organizationId: Long): List<Department> {
logger.info("Department find: organizationId={}", organizationId)
val departments = repository.findByOrganization(organizationId)
departments.forEach { it.employees = employeeClient.findByDepartment(it.id) }
return departments
}
}
It is worth taking a look at HTTP client implementation. It has been discussed in the details in my last article about Micronaut Quick Guide to Microservice with Micronaut Framework.
@Client(id = "employee-service", path = "/employees")
interface EmployeeClient {
@Get("/department/{departmentId}")
fun findByDepartment(departmentId: Long): MutableList<Employee>
}
You can run all the microservice using IntelliJ. You may also build the whole project with Maven using mvn clean install
command, and then run them using java -jar command. Thanks to maven-shade-plugin
applications will be generated as uber jars. Then run them in the following order: config-service
, discovery-service
and microservices.
$ java -jar config-service/target/config-service-1.0-SNAPSHOT.jar
$ java -jar discovery-service/target/discovery-service-1.0-SNAPSHOT.jar
$ java -jar employee-service/target/employee-service-1.0-SNAPSHOT.jar
$ java -jar department-service/target/department-service-1.0-SNAPSHOT.jar
$ java -jar organization-service/target/organization-service-1.0-SNAPSHOT.jar
After you may take a look at the Eureka dashboard available under address http://localhost:8761 to see the list of running services. You may also perform some tests by running HTTP API methods.
Summary
The sample applications source code is available on GitHub in the repository sample-micronaut-microservices in the branch kotlin. You can refer to that repository for more implementation details that has not been included in the article.
Leave a ReplyCancel reply