Kotline
Kotline
We walk through building a simple microservice using Kotlin and Spring Boot. It's
the new hotness!
You may find many examples of microservices built with Spring Boot on my blog, but
most of them are written in Java. With the rise in popularity of the Kotlin
language, it is more often used with Spring Boot for building backend services.
Starting with version 5, the Spring framework introduced first-class support for
Kotlin. In this article, I�m going to show you an example of microservices built
with Kotlin and Spring Boot 2. I�ll describe some interesting features of Spring
Boot, which can be treated as a set of good practices when building backend, REST-
based microservices.
<build>
<sourceDirectory>${project.basedir}/src/main/kotlin</sourceDirectory>
<testSourceDirectory>${project.basedir}/src/test/kotlin</testSourceDirectory>
<plugins>
<plugin>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-maven-plugin</artifactId>
<configuration>
<args>
<arg>-Xjsr305=strict</arg>
</args>
<compilerPlugins>
<plugin>spring</plugin>
</compilerPlugins>
</configuration>
<dependencies>
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-maven-allopen</artifactId>
<version>${kotlin.version}</version>
</dependency>
</dependencies>
</plugin>
</plugins>
</build>
We should also include some core Kotlin libraries like kotlin-stdlib-jdk8and
kotlin-reflect. They are provided by default for a Kotlin project on
start.spring.io. For REST-based applications, you will also need the Jackson
library for JSON serialization/deserialization. Of course, we have to include
Spring starters for web applications together with Actuator, which is responsible
for providing management endpoints.
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.module</groupId>
<artifactId>jackson-module-kotlin</artifactId>
</dependency>
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-reflect</artifactId>
</dependency>
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-stdlib-jdk8</artifactId>
</dependency>
We use the latest stable version of Spring Boot with Kotlin 1.2.71
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.2.RELEASE</version>
</parent>
<properties>
<java.version>1.8</java.version>
<kotlin.version>1.2.71</kotlin.version>
</properties>
2. Building the Application
Let�s begin with the basics. If you are familiar with Spring Boot and Java, the
biggest difference is in the main class declaration. You will call runApplication
method outside the Spring Boot application class. The main class, the same as in
Java, is annotated with @SpringBootApplication.
@SpringBootApplication
class SampleSpringKotlinMicroserviceApplication
fun main(args: Array<String>) {
runApplication<SampleSpringKotlinMicroserviceApplication>(*args)
}
Our sample application is very simple. It exposes some REST endpoints providing
CRUD operations for the model object. Even with this fragment of code, illustrating
controller implementation, you can see some nice Kotlin features. We may use a
shortened function declaration with an inferred return type. The annotation
@PathVariable does not require any arguments. The input parameter name is
considered to be the same as the variable name. Of course, we are using the same
annotations as with Java. In Kotlin, every property declared as having non-null
type must be initialized in the constructor. So, if you are initializing it using
dependency injection it has to declared as lateinit. Here�s the implementation of
PersonController.
@RestController
@RequestMapping("/persons")
class PersonController {
@Autowired
lateinit var repository: PersonRepository
@GetMapping("/{id}")
fun findById(@PathVariable id: Int): Person? = repository.findById(id)
@GetMapping
fun findAll(): List<Person> = repository.findAll()
@PostMapping
fun add(@RequestBody person: Person): Person = repository.save(person)
@PutMapping
fun update(@RequestBody person: Person): Person = repository.update(person)
@DeleteMapping("/{id}")
fun remove(@PathVariable id: Int): Boolean = repository.removeById(id)
}
Kotlin automatically generates getters and setters for class properties declared as
var. Also if you declare the model as a data class it generates equals, hashCode,
and toString methods. The declaration of our model class Personis very concise as
shown below.
data class Person(var id: Int, var name: String, var age: Int, var gender: Gender)
I have implemented my own in-memory repository class. I use Kotlin extensions for
manipulating the list of elements. This built-in Kotlin feature is similar to Java
streams, with the difference that you don�t have to perform any conversion between
Collection and Stream.
@Repository
class PersonRepository {
val persons: MutableList<Person> = ArrayList()
fun findById(id: Int): Person? {
return persons.singleOrNull { it.id == id }
}
fun findAll(): List<Person> {
return persons
}
fun save(person: Person): Person {
person.id = persons.size + 1
persons.add(person)
return person
}
fun update(person: Person): Person {
val index = persons.indexOf(person)
persons[index] = person
return person
}
fun removeById(id: Int): Boolean {
return persons.removeIf { it.id == id }
}
}
The sample application source code is available on the GitHub in repository, here.
management.endpoints.web.exposure.include: '*'
We can customize Actuator endpoints to provide more details about our app. A good
practice is to expose information about the version and git commit to the info
endpoint. As usual, Spring Boot provides auto-configuration for such features, so
the only thing we need to do is to include some Maven plugins to the build
configuration in pom.xml. The goal, build-info, is set for spring-boot-maven-plugin
and forces it to generate a properties file with basic information about the
version. The file is located in the directory, META-INF/build-info.properties. The
plugin git-commit-id-plugin will generate a git.propertiesfile in the root
directory.
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>build-info</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>pl.project13.maven</groupId>
<artifactId>git-commit-id-plugin</artifactId>
<configuration>
<failOnNoGitDirectory>false</failOnNoGitDirectory>
</configuration>
</plugin>
Now you should just build your application using mvn clean installcommand and then
run it.
{
"git":{
"commit":{
"time":"2019-01-14T16:20:31Z",
"id":"f7cb437"
},
"branch":"master"
},
"build":{
"version":"1.0-SNAPSHOT",
"artifact":"sample-spring-kotlin-microservice",
"name":"sample-spring-kotlin-microservice",
"group":"pl.piomin.services",
"time":"2019-01-15T09:18:48.836Z"
}
}
4. Enabling API Documentation
Build info and git properties may be easily injected into the application code. It
can be useful in some cases. One of those cases is if you have enabled auto-
generated API documentation. The most popular tools for this is Swagger. You can
easily integrate Swagger 2 with Spring Boot using the SpringFox Swagger project.
First, you need to include the following dependencies to your pom.xml.
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>2.9.2</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>2.9.2</version>
</dependency>
Then, you should enable Swagger by annotating the configuration class with
@EnableSwagger2. The required information is available inside the beans,
BuildProperties and GitProperties. We just have to inject them into the Swagger
configuration class as shown below. We set them as optional to prevent application
startup failure in case they are not present on the class path.
@Configuration
@EnableSwagger2
class SwaggerConfig {
@Autowired
lateinit var build: Optional<BuildProperties>
@Autowired
lateinit var git: Optional<GitProperties>
@Bean
fun api(): Docket {
var version = "1.0"
if (build.isPresent && git.isPresent) {
var buildInfo = build.get()
var gitInfo = git.get()
version = "${buildInfo.version}-${gitInfo.shortCommitId}-$
{gitInfo.branch}"
}
return Docket(DocumentationType.SWAGGER_2)
.apiInfo(apiInfo(version))
.select()
.apis(RequestHandlerSelectors.any())
.paths{ it.equals("/persons")}
.build()
.useDefaultResponseMessages(false)
.forCodeGeneration(true)
}
@Bean
fun uiConfig(): UiConfiguration {
return UiConfiguration(java.lang.Boolean.TRUE, java.lang.Boolean.FALSE, 1,
1, ModelRendering.MODEL, java.lang.Boolean.FALSE, DocExpansion.LIST,
java.lang.Boolean.FALSE, null, OperationsSorter.ALPHA, java.lang.Boolean.FALSE,
TagsSorter.ALPHA, UiConfiguration.Constants.DEFAULT_SUBMIT_METHODS, null)
}
private fun apiInfo(version: String): ApiInfo {
return ApiInfoBuilder()
.title("API - Person Service")
.description("Persons Management")
.version(version)
.build()
}
}
The documentation is available under the context path,/swagger-ui.html. Besides API
documentation, it displays the information about the application version, git
commit id, and branch name.
Image title
<profiles>
<profile>
<id>tomcat</id>
<activation>
<activeByDefault>true</activeByDefault>
</activation>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
</profile>
<profile>
<id>jetty</id>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jetty</artifactId>
</dependency>
</dependencies>
</profile>
<profile>
<id>undertow</id>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-undertow</artifactId>
</dependency>
</dependencies>
</profile>
</profiles>
Now, if you would like to enable servers other than Tomcat for your application,
you should activate the appropriate profile during the Maven build.
Conclusion
The development of microservices using Kotlin and Spring Boot is nice and simple.
Based on this sample application, I have introduced the main Spring Boot features
for Kotlin. I also described some good practices you may apply to your
microservices when building them using Spring Boot and Kotlin. You can compare the
described approach with some other micro-frameworks used with Kotlin, for example,
Ktor, described in one of my previous articles: Kotlin Microservices with Ktor.