DEV Community

Cover image for 🥈 Quarkus: Top 5 Server-Side Frameworks for Kotlin in 2022
Roger Viñas Alcon
Roger Viñas Alcon

Posted on • Updated on

🥈 Quarkus: Top 5 Server-Side Frameworks for Kotlin in 2022

This is a demo inspired by @antonarhipov's Top 5 Server-Side Frameworks for Kotlin in 2022 @ Kotlin by JetBrains where, spoiler alert, the author shares this top 5 list:

🥇 Spring Boot
🥈 Quarkus
🥉 Micronaut
🏅 Ktor
🏅 http4k

I have a lot of experience in Spring Boot, so I wanted to take a look at the other ones 😜
Meme

To do so we will create a simple application with each one of these frameworks, implementing the following scenario:
Scenario

GitHub logo rogervinas / top-5-server-side-kotlin-frameworks-2022

⭐ Top 5 Server-Side Frameworks for Kotlin in 2022

This post will describe the step-by-step Quarkus implementation, you can check the other ones in this series too.

To begin with you can follow the Quarkus quick start. You will see that there is a quarkus command line (easily installable via sdkman) to create an application choosing Gradle or Maven as the build tool. Once the application is created we can use both quarkus command line or gradlew/mvnw.

You can also check Creating your first application as well as all the other guides.

To create a simple application with a reactive REST endpoint:



sdk install quarkus
quarkus create app org.rogervinas:quarkus-app \
  --gradle-kotlin-dsl --java=17 --kotlin \
  --extension='kotlin,resteasy-reactive-jackson'


Enter fullscreen mode Exit fullscreen mode

A Gradle project will be created with the following:

  • src/main/docker: a few Dockerfiles with different options to create the application docker image.
  • src/main: main sources with a template GreetingResource implementing a REST endpoint on /hello.
  • src/test: test sources with an integration test of the endpoint.
  • src/native-test: test sources with the same integration test of the endpoint but starting the application using the docker native image.

Just run it once to check everything is ok:



quarkus dev


Enter fullscreen mode Exit fullscreen mode

And make a request to the endpoint:



curl http://localhost:8080/hello
Hello from RESTEasy Reactive


Enter fullscreen mode Exit fullscreen mode

We can remove the src/main/resources/META-INF directory containing some HTML files that we will not need.

Implementation

YAML configuration

To use yaml configuration files we need to add this extension:



quarkus extension add quarkus-config-yaml


Enter fullscreen mode Exit fullscreen mode

And then rename application.properties to application.yaml and add our first configuration property:



greeting:
  name: "Bitelchus"


Enter fullscreen mode Exit fullscreen mode

Note that in Quarkus we have these Default profiles (similar to "profiles" in Spring Boot):

  • dev - Activated when in development mode (i.e. quarkus:dev).
  • test - Activated when running tests.
  • prod - The default profile when not running in development or test mode.

So we will create a application-prod.yaml file to put there all the production configuration properties.

More documentation about configuration sources at Configuration Reference guide.

GreetingRepository

We will create a GreetingRepository:



interface GreetingRepository {
  fun getGreeting(): String
}

@ApplicationScoped
class GreetingJdbcRepository(
  private val client: PgPool
): GreetingRepository {
  override fun getGreeting(): String = client
    .query(
      """
        SELECT greeting
        FROM greetings 
        ORDER BY random() LIMIT 1
      """.trimIdent()
    )
    .executeAndAwait()
    .map { r -> r.get(String::class.java, "greeting") }
    .first()
}


Enter fullscreen mode Exit fullscreen mode
  • The @ApplicationScoped annotation will make Quarkus to create an instance at startup.
  • We inject the Reactive SQL Client.
  • We use query and that SQL to retrieve one random greeting from the greetings table.

For this to work, we need some extra steps ...

Add the Reactive SQL Client extension:



quarkus extension add quarkus-reactive-pg-client


Enter fullscreen mode Exit fullscreen mode

Configure it for dev and test profiles in application.yaml:



quarkus:
  datasource:
    devservices:
      image-name: "postgres:14.5"


Enter fullscreen mode Exit fullscreen mode

Configure it for prod profile in application-prod.yaml:



quarkus:
  datasource:
    db-kind: "postgresql"
    username: "myuser"
    password: "mypassword"
    reactive:
      url: "postgresql://${DB_HOST:localhost}:5432/mydb"
      max-size: 20


Enter fullscreen mode Exit fullscreen mode

Note that for dev and test profiles we just use something called "Dev Services", meaning it will automatically start containers and configure the application to use them. You can check the Dev Services Overview and Dev Services for Databases documentation.

To enable Flyway we need to add these dependencies manually (apparently there is no extension):



implementation("io.quarkus:quarkus-flyway")
implementation("io.quarkus:quarkus-jdbc-postgresql")


Enter fullscreen mode Exit fullscreen mode

And, as it seems that we cannot use the same reactive datasource, we will have to configure the standard one:

  • application.yaml ```yaml

quarkus:
flyway:
migrate-at-start: true

* `application-prod.yaml`
```yaml


quarkus:
  datasource:
    jdbc:
      url: "jdbc:postgresql://${DB_HOST:localhost}:5432/mydb"
      max-size: 20


Enter fullscreen mode Exit fullscreen mode

So quarkus.datasource.jdbc will be used by Flyway and quarkus.datasource.reactive by the application.

Finally, Flyway migrations under src/main/resources/db/migration to create and populate greetings table.

GreetingController

We will rename the generated GreetingResource class to GreetingController, so it looks like the Spring Boot one:



@Path("/hello")
class GreetingController(
  private val repository: GreetingRepository,
  @ConfigProperty(name = "greeting.name")
  private val name: String,
  @ConfigProperty(
    name = "greeting.secret", 
    defaultValue = "unknown"
  )
  private val secret: String
) {
  @GET
  @Produces(MediaType.TEXT_PLAIN)
  fun hello() {
    val greeting = repository.getGreeting()
    return "$greeting my name is $name and my secret is $secret"
  }
}


Enter fullscreen mode Exit fullscreen mode
  • We can inject dependencies via constructor and configuration properties using @ConfigProperty annotation.
  • We expect to get greeting.secret from Vault, that is why we configure unknown as its default value, so it does not fail until we configure Vault properly.
  • Everything is pretty similar to Spring Boot. Note that it uses standard JAX-RS annotations which is also possible in Spring Boot (but not by default).

Vault configuration

Following the Using HashiCorp Vault guide we add the extension:



quarkus extension add vault


Enter fullscreen mode Exit fullscreen mode

For dev and test profiles we configure Vault "Dev Service" in application.yaml:



quarkus:
  vault:
    secret-config-kv-path: "myapp"
    devservices:
      image-name: "vault:1.12.1"
      init-commands:
        - "kv put secret/myapp greeting.secret=watermelon"


Enter fullscreen mode Exit fullscreen mode

Note that here we can use these init-commands to populate Vault 🥹

For prod profile we configure Vault in application-prod.yaml:



quarkus:
  vault:
    url: "http://${VAULT_HOST:localhost}:8200"
    authentication:
      client-token: "mytoken"


Enter fullscreen mode Exit fullscreen mode

Testing the endpoint

We rename the original GreetingResourceTest to GreetingControllerTest and we modify it this way:



@QuarkusTest
@TestHTTPEndpoint(GreetingController::class)
class GreetingControllerTest {

  @InjectMock
  private lateinit var repository: GreetingRepository

  @Test
  fun `should say hello`() {
    doReturn("Hello").`when`(repository).getGreeting()

    `when`()
      .get()
      .then()
      .statusCode(200)
      .body(
        `is`(
          "Hello my name is Bitelchus and my secret is watermelon"
        )
      )
    }
}


Enter fullscreen mode Exit fullscreen mode
  • @QuarkusTest will start all "Dev Services", despite the database not being used 🤷
  • We mock the repository with @InjectMock.
  • We use RestAssured to test the endpoint.
  • As this test uses Vault, the secret should be watermelon.

Testing the application

We can test the whole application this way:



@QuarkusTest
class GreetingApplicationTest {

  @Test
  fun `should say hello`() {
    given()
      .`when`().get("/hello")
      .then()
      .statusCode(200)
      .body(
        matchesPattern(
          ".+ my name is Bitelchus and my secret is watermelon"
        )
      )
    }
}


Enter fullscreen mode Exit fullscreen mode
  • @QuarkusTest will start all "Dev Services", now all of them are being used.
  • We use RestAssured to test the endpoint.
  • We use pattern matching to check the greeting, as it is random.
  • As this test uses Vault, the secret should be watermelon.

Test



./gradlew test


Enter fullscreen mode Exit fullscreen mode

Run



# Start Application with "Dev Services"
quarkus dev

# Make requests
curl http://localhost:8080/hello


Enter fullscreen mode Exit fullscreen mode

Build a fatjar and run it



# Build fatjar
quarkus build

# Start Vault and Database
docker compose up -d vault vault-cli db

# Start Application
java -jar build/quarkus-app/quarkus-run.jar

# Make requests
curl http://localhost:8080/hello

# Stop Application with control-c

# Stop all containers
docker compose down


Enter fullscreen mode Exit fullscreen mode

Build a docker image and run it



# Build docker image
quarkus build
docker build -f src/main/docker/Dockerfile.jvm -t quarkus-app . 

# Start Vault and Database
docker compose up -d vault vault-cli db

# Start Application
docker compose --profile quarkus up -d

# Make requests
curl http://localhost:8080/hello

# Stop all containers
docker compose --profile quarkus down
docker compose down


Enter fullscreen mode Exit fullscreen mode

Build a native executable and run it

Following Build a Native Executable:



# Install GraalVM via sdkman
sdk install java 22.3.r19-grl
sdk default java 22.3.r19-grl
export GRAALVM_HOME=$JAVA_HOME

# Install the native-image
gu install native-image

# Build native executable
quarkus build --native

# Start Vault and Database
docker compose up -d vault vault-cli db

# Start Application using native executable
./build/quarkus-app-1.0.0-SNAPSHOT-runner

# Make requests
curl http://localhost:8080/hello

# Stop Application with control-c

# Stop all containers
docker compose down


Enter fullscreen mode Exit fullscreen mode

That's it! Happy coding! 💙

Top comments (0)