Reader small image

You're reading from  Kotlin Design Patterns and Best Practices - Third Edition

Product typeBook
Published inApr 2024
PublisherPackt
ISBN-139781805127765
Edition3rd Edition
Right arrow
Author (1)
Alexey Soshin
Alexey Soshin
author image
Alexey Soshin

Alexey Soshin is a software architect with 18 years of experience in the industry. He started exploring Kotlin when Kotlin was still in beta, and since then has been a big enthusiast of the language. He's a conference speaker, published writer, and the author of a video course titled Pragmatic System Design
Read more about Alexey Soshin

Right arrow

Concurrent Microservices with Ktor

In the previous chapter, we explored practical applications of the Arrow framework through some concrete examples, but we didn’t put a complete application together. This chapter will apply the skills we’ve acquired so far by building a complete microservice. We aim for this microservice to be reactive and to mirror real-life scenarios. To achieve this, we’ll employ the Ktor framework, whose benefits we’ll enumerate in this chapter’s first section.

In this chapter, we will cover the following topics:

  • Getting started with Ktor
  • Routing requests
  • Connecting to a database
  • Configuration management in Ktor
  • CRUD operations on entities
  • Testing CRUD operations
  • Organizing routes in Ktor
  • Achieving concurrency in Ktor

By the end of this chapter, you’ll have developed a Kotlin-based microservice that is thoroughly tested, as well as capable of reading data...

Technical requirements

This chapter introduces the use of a PostgreSQL database. To sidestep the requirement of installing a specific version of PostgreSQL on your local machine, a Docker Compose configuration file is provided.

If you don’t have Docker installed, installation instructions can be found here: https://docs.docker.com/get-docker/.

The source code for this chapter is available at https://github.com/PacktPublishing/Kotlin-Design-Patterns-and-Best-Practices_Third-Edition/tree/main/Chapter11.

Getting started with Ktor

You might be weary of developing commonplace applications like to-do or shopping lists.

Thus, in this chapter, we will design a microservice for a cat shelter. The microservice will have capabilities to:

  • Offer an endpoint to verify the service’s operational status.
  • Display a list of cats currently residing in the shelter.
  • Enable the addition of new cats.
  • Update cat information.
  • Remove a cat added by mistake from the registry.

For this project, we will utilize Ktor, a concurrent framework developed and maintained by the creators of the Kotlin programming language.

The easiest way to create a Ktor application nowadays is by going to https://start.ktor.io and generating a new project.

Figure 11.1: Project generation on start.ktor.io

For the moment, exclude all additional plugins; we will introduce them gradually later, explaining the purpose of each.

After downloading the resulting...

Routing requests

Now, let’s examine the routing block more closely:

routing {
    get("/") {
        call.respondText("Hello World!")
    }
}

This block defines all the URLs that our server will handle. Currently, it only manages the root URL. When this URL is requested, the server returns a text response, “OK”, to the user.

Next, we explore how to return a JSON response:

get("/status") {
    call.respond(mapOf("status" to "OK"))
}

Instead of the respondText() method, we use respond(), which accepts an object rather than a string. In this example, we pass a map of strings. However, running this code as is will cause an exception, as objects aren’t automatically serialized into JSON. We can overcome this by using the kotlinx-serialization and ktor-server-content-negotiation libraries. We begin by adding them to our dependencies:

dependencies {
    ...
    implementation("io...

Connecting to a database

To manage and access cats in our application, a database connection is necessary. We’ll use PostgreSQL, but the process is similar for other SQL databases.

Firstly, we require a library to facilitate this connection. We’ll employ the Exposed library by JetBrains, offering a Kotlin-friendly approach to interacting with relational databases.

Start by adding the following dependencies to the build.gradle.kts file:

...
val exposedVersion = "0.48.0"
 
dependencies {
    ...
    implementation("org.jetbrains.exposed:exposed-core:$exposedVersion")
    implementation("org.jetbrains.exposed:exposed-dao:$exposedVersion")
    implementation("org.jetbrains.exposed:exposed-jdbc:$exposedVersion")
    implementation("org.postgresql:postgresql:42.5.1")
}

With the libraries set up, the next step is establishing the database connection. Create a new file named DB.kt in /src/main/kotlin and include...

Configuration management in Ktor

Configuration management is a key aspect of customizing applications in Ktor, as it enables you to define and organize crucial settings, including server ports, database credentials, and API keys. Ktor, by default, searches for an application.conf file in the src/main/resources directory. We’ll be working with a file in the HOCON (Human-Optimized Config Object Notation) format, although Ktor also accommodates other formats like YAML.

HOCON is particularly effective because it offers a user-friendly approach to configuration. It is a human-readable and -writable format, designed to simplify the process of configuring applications. HOCON’s structure and syntax make it straightforward for developers to manage complex configurations, enhancing both efficiency and clarity.

This file typically includes configuration such as server port, host, and settings for various modules. You can access these configuration values in Ktor using environment...

Organizing routes in Ktor

In Ktor, structuring multiple routes belonging to the same domain can be streamlined for better organization and readability. Our current routing{} block includes various endpoints, some of which are related to cats.

Here’s how our routing block looks currently:

routing {
    get("/status") {
        ...
    }
    post("/cats") {
        ...
    }
    get("/cats") {
        ...
    }
    get("/cats/{id}") {
        ...
    }
}

To improve the structure, we can extract all cat-related routes into a separate function:

fun Routing.catsRoutes() {
    ...
}

In IntelliJ IDEA, there’s even an option to automatically generate an extension function on the Routing class.

To tidy this up, we replace the cat-related routes with a dedicated function:

routing {
    get("/status") {
        ...
    }
    catsRoutes()
}

We can now consolidate all our cat-related routes into...

Achieving concurrency in Ktor

Looking back at the code we’ve written in this chapter, you may be under the impression that the Ktor code is not concurrent at all. However, this couldn’t be further from the truth.

All Ktor functions used in this chapter are built upon coroutines and the concept of suspending functions. For each incoming request, Ktor initiates a new coroutine to handle it. This functionality is inherently supported by the CIO server engine, which is fundamentally based on coroutines. Ktor’s concurrency model is designed to be efficient yet unobtrusive, a crucial aspect of its architecture.

Furthermore, the routing blocks, which define our endpoints, have access to CoroutineScope. This allows us to invoke suspending functions within these blocks. An example of such a suspending function is call.respond(), frequently used in our examples. Suspending functions offer opportunities for context switching and concurrent execution of other code...

Summary

In this chapter, we’ve developed a thoroughly tested service using Kotlin, employing the Ktor framework for web functionalities and Exposed for database operations. We delved into Ktor’s use of design patterns, such as Factory, Singleton, and Bridge, which offer a flexible and well-organized framework for our code.

We delved into database interaction using the Exposed framework, learning how to declare, create, and drop tables, as well as how to insert, fetch, and delete entities. Additionally, we’ve seen the application of Ktor’s HTTP client to make requests to external services, enriched by its support for JSON serialization and asynchronous operations.

We briefly touched on the Exposed framework’s features. Apart from the DSL API used in this chapter, Exposed also offers a DAO API, allowing interaction with objects instead of writing DSL queries. For further reading, see https://github.com/JetBrains/Exposed/wiki.

Moreover,...

Questions

  1. How are the Ktor applications structured, and what are their benefits?
  2. What are plugins in Ktor, and what are they used for?
  3. What is the main problem that the Exposed library solves?

Learn more on Discord

Join our community’s Discord space for discussions with the author and other readers:

https://discord.com/invite/xQ7vVN4XSc

lock icon
The rest of the chapter is locked
You have been reading a chapter from
Kotlin Design Patterns and Best Practices - Third Edition
Published in: Apr 2024Publisher: PacktISBN-13: 9781805127765
Register for a free Packt account to unlock a world of extra content!
A free Packt account unlocks extra newsletters, articles, discounted offers, and much more. Start advancing your knowledge today.
undefined
Unlock this book and the full library FREE for 7 days
Get unlimited access to 7000+ expert-authored eBooks and videos courses covering every tech area you can think of
Renews at $15.99/month. Cancel anytime

Author (1)

author image
Alexey Soshin

Alexey Soshin is a software architect with 18 years of experience in the industry. He started exploring Kotlin when Kotlin was still in beta, and since then has been a big enthusiast of the language. He's a conference speaker, published writer, and the author of a video course titled Pragmatic System Design
Read more about Alexey Soshin