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

Reactive Microservices with Vert.x

In the previous chapter, we familiarized ourselves with the Ktor framework and, with it, we created a web service that could store cats in its database.

Picking up from our previous work, this chapter shifts our focus to using the Vert.x framework with Kotlin. In this chapter, we’ll write a similar service to the one we wrote in the previous chapter, using the Vert.x framework instead.

Vert.x, a Reactive framework, ties in with the principles we discussed in Chapter 7, Controlling the Data Flow. It brings to the table numerous advantages that meet our project’s needs, such as increased scalability, a non-blocking development model, and the ability to handle many concurrent data flows efficiently. These attributes make Vert.x an excellent option for developing microservices that are both responsive and robust.

In this chapter, we will not only enumerate the benefits of Vert.x but also explore its practical uses. We aim to...

Technical requirements

Like the previous chapter, this chapter will also assume that you have Docker already installed and that you have basic knowledge of working with it. We’ll also use the same table structure we created with Ktor.

You can find the full source code for this chapter here: https://github.com/PacktPublishing/Kotlin-Design-Patterns-and-Best-Practices_Third-Edition/tree/main/Chapter12.

Getting started with Vert.x

Vert.x is a Reactive framework that is asynchronous and non-blocking. Let’s understand what this means by looking at a concrete example.

We’ll start by creating a new Kotlin Gradle project. You can follow the steps from the previous chapter for that. Alternatively, you can also generate a new project by using start.vertx.io.

Next, add the following dependencies to your build.gradle.kts file:

val vertxVersion = "4.5.6"
dependencies {
    implementation(platform("io.vertx:vertx-stack-depchain:$vertxVersion"))
    implementation("io.vertx:vertx-web")
    implementation("io.vertx:vertx-lang-kotlin")
    implementation("io.vertx:vertx-lang-kotlin-coroutines")
}

Similar to what we discussed in the previous chapter, all the dependencies must be of the same version to avoid any conflicts. That’s the reason why we are using a variable for the library version—to be able...

Routing requests

Notice that no matter which URL we specify, we always get the same result. Of course, that’s not what we want to achieve. Let’s start by adding the most basic endpoint, which will only tell us that the service is up and running:

fun main() {
    val vertx = Vertx.vertx()
    vertx.createHttpServer().requestHandler{ ctx ->
        ctx.response().end("OK")
    }.listen(8081)
    println("open http://localhost:8081")
}

This code is designed to produce the same response for any type of request, whether it’s a GET or POST, and irrespective of the URL. Typically, this isn’t the desired behavior. In REST architecture, it’s common practice to define distinct paths for various actions. To facilitate this, we’ll employ the Router. The Router enables the definition of specific handlers for different HTTP methods and URLs.

Now, let’s add a /status endpoint that will return an HTTP status code...

Verticles

As our project progresses, the server.kt file, containing our current code, is growing increasingly large. To manage this, we need to separate different parts of the code. In Vert.x, this can be accomplished by organizing the code into distinct classes known as “verticles.”

You can think of a verticle as a lightweight actor. We discussed actors in Chapter 5, Introducing Functional Programming.

Let’s see how we can create a new verticle that will encapsulate our server:

class ServerVerticle : CoroutineVerticle() {
    override suspend fun start() {
        val router = router()
        vertx.createHttpServer()
            .requestHandler(router)
            .listen(8081)
        println("open http://localhost:8081/status")
    }
    private fun router(): Router {
        // Our router code comes here now  
        val router = Router.router(vertx)
        ...
        return router
    }
}

Every verticle has a start() method...

Handling requests

As we discussed earlier in this chapter, all requests in Vert.x are handled by the Router class. We covered the concept of routing in the previous chapter, so now, let’s just discuss the differences between the Ktor and Vert.x approaches to routing requests.

For now, we’ll set up two endpoints in our router: one for deleting a cat by its ID, and another for updating its details:

private fun router(): Router {
    // Our router code comes here now
    val router = Router.router(vertx)
    router.delete("/cats/:id").handler { ctx ->
        // Code for deleting a cat
    }
    router.put("/cats/:id").handler { ctx ->
        // Code for updating a cat
    }
    
    return router
}

Both endpoints receive a URL parameter. In Vert.x, we use a colon notation for this.

To be able to parse JSON requests and responses, Vert.x has a BodyHandler class. Now, let’s declare it as well. This should come just after...

Testing Vert.x applications

To test our Vert.x application, we’ll use the JUnit 5 framework, which we discussed in the previous chapter.

You’ll need the following two dependencies in your build.gradle.kts file:

dependencies {
    ...
    testImplementation("io.vertx:vertx-junit5")
    testImplementation("org.junit.jupiter:junit-jupiter:5.9.1")
    testImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-
    test:1.8.0")
}

Our first test will be located in the /src/test/kotlin/ServerTest.kt file.

The basic structure of all the integration tests looks something like this:

@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class ServerTest {
    private val vertx: Vertx = Vertx.vertx()
    @BeforeAll
    fun setup() = runTest {
        vertx.deployVerticle(ServerVerticle()).coAwait()
    }
    @AfterAll
    fun tearDown() {
        // You want to stop your server once
        vertx.close()
    }
    
    @Test
    fun...

Working with databases

To be able to progress further with our tests, we need the ability to create entities in the database. For that, we’ll need to connect to the database.

First, let’s add the following two lines to our build.gradle.kts dependencies section:

dependencies {
    ...
    implementation("org.postgresql:postgresql:42.5.1")
    implementation("io.vertx:vertx-pg-client")
    implementation("com.ongres.scram:client:2.1")
}

The first line introduces the PostgreSQL driver into our project, a necessary component for interfacing with PostgreSQL databases. The second line adds the Vert.x JDBC client, enabling Vert.x to interface with any JDBC-supported database using this driver. The third line integrates the SCRAM authentication mechanism, a security feature often used in modern PostgreSQL setups, ensuring secure database access.

Now, we want to hold the database configuration somewhere. For local development, it...

Understanding Event Loop

The goal of Event Loop is to continuously check for new events in a queue, and each time a new event comes in, to quickly dispatch it to a function that knows how to handle it. This way, a single thread or a very limited number of threads can handle a huge number of events.

In the case of web frameworks such as Vert.x, events may be requests to our server.

To understand the concept of the Event Loop better, let’s go back to our server code and attempt to implement an endpoint for deleting a cat:

val db = Db.connect(vertx)
router.delete("/:id").handler { ctx ->
    val id = ctx.request().getParam("id").toInt()
    db.preparedQuery("DELETE FROM cats WHERE ID = $1")
        .execute(Tuple.of(id))
        .await()
    ctx.end()
}

This code is very similar to what we’ve written in our tests in the previous section. We read the URL parameter from the request using the getParam() function, then we pass...

Communicating with Event Bus

Event Bus is an implementation of the Observable design pattern, which we discussed in Chapter 4, Getting Familiar with Behavioral Patterns.

We’ve already mentioned that Vert.x is based on the concept of verticles, which are isolated actors. We’ve already seen the other types of actors in Chapter 6, Threads and Coroutines. Kotlin’s coroutines library provides the actor() and producer() coroutine generators, which create a coroutine bound to a channel.

Similarly, all the verticles in the Vert.x framework are bound by Event Bus and can pass messages to one another using it. Now, let’s extract the code from our ServerVerticle class into a new class, which we’ll call CatVerticle.

Any verticle can send a message over Event Bus by choosing between the following methods:

  • request() will send a message to only one subscriber and wait for a response.
  • send() will send a message to only one subscriber...

Summary

This chapter concludes our journey into the design patterns in Kotlin. Vert.x uses actors, called verticles, to organize the logic of the application. Actors communicate between themselves using Event Bus, which is an implementation of the Observable design pattern.

We also discussed the Event Loop pattern, how it allows Vert.x to process lots of events concurrently, and why it’s important not to block its execution.

Now, you should be able to write microservices in Kotlin using two different frameworks, and you can choose what approach works best for you.

Vert.x provides a lower-level API than Ktor, which means that we may need to think more about how we structure our code, but the resulting application may be more performant as a result. As often happens, it’s a tradeoff between performance and developer experience. Do you want to work with a database in a typesafe manner? Looking for the most idiomatic Kotlin framework? Then pick Ktor. But if you...

Questions

  1. What’s a verticle in Vert.x?
  2. What’s the goal of Event Bus?
  3. Why shouldn’t we block the Event Loop?

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 €14.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