Generic Top

I just announced the new Learn Spring course, focused on the fundamentals of Spring 5 and Spring Boot 2:

>> CHECK OUT THE COURSE

1. Overview

In this tutorial, we’re going to develop a reactive microservice application with Kotlin and Spring Boot.

Our application will expose a REST API, persist data in a database and have endpoints for monitoring.

2. Use Case

These days many of us are struggling with health problems, hence we've chosen a health tracker application for our tutorial. It allows people to create their health profile, save symptoms like fever, blood pressure and so on. Later, users can see reports about their health logs.

Let's stay safe and healthy and continue our journey.

3. Application Setup

First, we’re going to set up project dependencies. Here we use Maven, but both Maven and Gradle are suitable for us.

Our application inherits from spring-boot-starter-parent:

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.2.5.RELEASE</version>
    <relativePath/>
</parent>

Then let's add basic dependencies to the pom.xml that will allow us to work with Kotlin and Java:

<dependency>
    <groupId>org.jetbrains.kotlin</groupId>
    <artifactId>kotlin-reflect</artifactId>
    <version>1.3.70</version>
</dependency>
<dependency>
    <groupId>org.jetbrains.kotlin</groupId>
    <artifactId>kotlin-stdlib-jdk8</artifactId>
    <version>1.3.70</version>
</dependency>

Now we have to add dependencies for REST API and persistence.

We ‘ll use Spring Reactive Web for reactive REST API, so we add dependencies of spring-boot-starter-webflux and jackson-module-kotlin.

Reactive options are also available for data persistence, and at the moment, there is an experimental version of Spring Data R2DBC available in the Spring Milestone repository. Let's add that:

<repositories>
   ...
   <repository>
       <id>spring-milestone</id>
       <name>Spring Milestone Repository</name>
       <url>http://repo.spring.io/milestone</url>
   </repository>
</repositories>

We also import the spring-boot-bom-r2dbc in dependencyManagement section, then add spring-boot-starter-data-r2dbc to dependencies:

<dependencyManagement>
    <dependencies>
        ...
        <dependency>
            <groupId>org.springframework.boot.experimental</groupId>
            <artifactId>spring-boot-bom-r2dbc</artifactId>
            <version>0.1.0.M3</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>

Now we have to choose our database. In this tutorial, we’re using H2 as an in-memory database, but R2DBC drivers for Postgres, MySQL, and Microsoft SQL Server are also available at this time.

Here we add the r2dbc-h2 dependency:

<dependency>
    <groupId>io.r2dbc</groupId>
    <artifactId>r2dbc-h2</artifactId>
</dependency>

At last, to finish with dependencies, we’re going to add the spring-boot-starter-actuator to provide monitoring and management API for our application.

Finally, let's create the Application class:

@SpringBootApplication
class HealthTrackerApplication

fun main(args: Array<String>) {
    runApplication<HealthTrackerApplication>(*args)
}

We should be aware that the runApplication<HealthTrackerApplication>(*args) is a short form of SpringApplication.run(HealthTrackerApplication::class.java, *args).

4. Model

Let's model the Profile data class which encapsulates the user profile data:

@Table 
data class Profile(@Id var id:Long?, var firstName : String, var lastName : String,
  var birthDate: LocalDateTime)

When we use @Table without a table name, Spring generates the table name from the class name according to naming conventions. @Id is the marker for the primary key.

Then, we have the HealthRecord data class to encapsulate the health symptoms of a profile:

@Table
data class HealthRecord(@Id var id: Long?, var profileId: Long?, var temperature: Double,
  var bloodPressure: Double, var heartRate: Double, var date: LocalDate)

The HealthRecord has a dependency on Profile class, but unfortunately, entity association is not supported by Spring Data R2DBC right now, so we've used the profileId instead of the Profile instance.

5. Database Configuration

At the moment, schema generation for Spring Data R2DBC is not available. So, we'll have to do it ourselves programmatically or with script files.

Here, let's go through the code and create a configuration class to execute the DDLs:

@Configuration
class DBConfiguration(db: DatabaseClient) {
    init {
        val initDb = db.execute {
            """ CREATE TABLE IF NOT EXISTS profile (
                    id SERIAL PRIMARY KEY,
                    //other columns specifications
                );
                CREATE TABLE IF NOT EXISTS health_record(
                    id SERIAL PRIMARY KEY,
                    profile_id LONG NOT NULL,
                    //other columns specifications
                );
            """
        }
        initDb.then().subscribe()
    }
}

Now, we're ready to set up persistence.

6. Repositories

In this step, we’re going to create the required Repository interfaces. Let's extend the ProfileRepository interface from the ReactiveCrudRepository:

@Repository
interface ProfileRepository: ReactiveCrudRepository<Profile, Long>

ReactiveCrudRepository provides methods like save and findById.

Then we have the HealthRecordRepository with an extra method to return the list of health records for a profile.

Again at the moment, query derivation is not supported with Spring Data R2DBC, and we have to write queries manually:

@Repository
interface HealthRecordRepository: ReactiveCrudRepository<HealthRecord, Long> {
    @Query("select p.* from health_record p where p.profile_id = :profileId ")
    fun findByProfileId(profileId: Long): Flux<HealthRecord>
}

7. Controllers

Now, we need to expose the REST API to register a new profile. We also need endpoints to store and retrieve health records. For simplicity, we also reuse entities as data transfer objects.

Let's start with ProfileController, which exposes an API for the profile registration. We inject an instance of ProfileRepository via constructor in ProfileController:

@RestController
class ProfileController(val repository: ProfileRepository) {
    
    @PostMapping("/profile")
    fun save(@RequestBody profile: Profile): Mono<Profile> = repository.save(profile)
}

Then we go through health record endpoints, one to store the data, and one to return the averages.

Furthermore, let's have the AverageHealthStatus data class, which encapsulates the average health record of a profile:

class AverageHealthStatus(var cnt: Int, var temperature: Double, 
  var bloodPressure: Double, var heartRate: Double)

And here is the HealthRecordController:

@RestController
class HealthRecordController(val repository: HealthRecordRepository) {

    @PostMapping("/health/{profileId}/record")
    fun storeHealthRecord(@PathVariable("profileId") profileId: Long, @RequestBody record: HealthRecord):
      Mono<HealthRecord> =
        repository.save(record)

    @GetMapping("/health/{profileId}/avg")
    fun fetchHealthRecordAverage(@PathVariable("profileId") profileId: Long): Mono<AverageHealthStatus> =
        repository.findByProfileId(profileId)
            .reduce( /* logic to calculate total */)
            .map { s ->
                /* logic to calculate average from count and total */
            }

}

8. Monitoring Endpoints

Spring Boot Actuator exposes endpoints to monitor and manage our application. When we add the spring-boot-starter-actuator to dependencies, by default the /health and the /info endpoints are enabled. We can enable more endpoints by setting the value of the management.endpoints.web.exposure.include property in our application.yml.

Let's enable /health and /metrics for our application:

management.endpoints.web.exposure.include: health,metrics

We also can enable all endpoints by setting the value to *. Be aware that exposing all endpoints may cause security risks, hence we'd need additional security configurations.

We can see a list of all enabled actuator endpoints by calling /actuator.

To check the health status of our application, let's call the http://localhost:8080/actuator/health. The response should be:

{
    "status": "UP"
}

which means that our application is running properly.

9. Testing

We can use WebTestClient to test our endpoints. So as a practical sample, let's test the profile API.

First, we create a ProfileControllerTest class and annotate it with @SpringBootTest:

@SpringBootTest
class ProfileControllerTest {}

When a class is annotated with @SpringBootTest, Spring will search in the class package and upward for a class annotated with @SpringBootConfiguration.

Then let's create an instance of WebTestClient and bind to it to ProfileController:

@Autowired
lateinit var controller: ProfileController

@BeforeEach
fun setup() {
    client = WebTestClient.bindToController(controller).build()
}

Now we've got everything ready to test our /profile endpoint:

@Test
fun whenRequestProfile_thenStatusShouldBeOk() {
    client.post()
      .uri("/profile")
      .contentType(MediaType.APPLICATION_JSON)
      .bodyValue(profile)
      .exchange()
      .expectStatus().isOk
}

10. Conclusion

We've finished our journey to create a microservice application with Kotlin and Spring Boot. Along the way, we learned how to expose REST API, monitor endpoints and manage persistence.

Advanced topics like security, service calls and API Gateway are for another day.

As always code of this tutorial is available over on GitHub.

Generic bottom

I just announced the new Learn Spring course, focused on the fundamentals of Spring 5 and Spring Boot 2:

>> CHECK OUT THE COURSE
2 Comments
Oldest
Newest
Inline Feedbacks
View all comments
Michał Regulski
Michał Regulski
7 months ago

You could show examples with suspend functions and TransactionalOperator

Loredana Crusoveanu
7 months ago

Hi Michał,
That is an interesting idea for a more advanced article on the topic.
This one is a basic tutorial to show how Kotlin and Spring Boot can together be used to build a reactive microservice.

Thanks for your suggestion though; we’ll look into it.

Cheers.

Comments are closed on this article!