🥇 Spring Boot: Top 5 Server-Side Frameworks for Kotlin in 2022

rogervinas

Roger Viñas Alcon

Posted on January 15, 2023

🥇 Spring Boot: 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 Spring Boot implementation, you can check the other ones in this series too.

We can create a project using Spring Initialzr and download it locally.

A lot of documentation guides at spring.io/spring-boot.

Implementation

YAML Configuration

By default, Spring Initialzr creates a template using application.properties file. We can just rename it to application.yaml and it will work the same.

We can put there our first configuration property:

greeting:
  name: "Bitelchus"
Enter fullscreen mode Exit fullscreen mode

More documentation about configuration sources at Externalized Configuration and Profiles.

GreetingRepository

We will create a GreetingRepository:

interface GreetingRepository {
  fun getGreeting(): String
}

@Repository
class GreetingJdbcRepository(
  private val jdbcTemplate: JdbcTemplate
): GreetingRepository {
  fun getGreeting(): String = jdbcTemplate
    .queryForObject(
      """
        SELECT greeting FROM greetings
        ORDER BY random() LIMIT 1
      """.trimIndent(), 
      String::class.java
    )!!
}
Enter fullscreen mode Exit fullscreen mode
  • The @Repository annotation will make Spring Boot to create a singleton instance at startup.
  • We inject a JdbcTemplate (provided by the spring-boot-starter-jdbc autoconfiguration) to execute queries to the database.
  • We use queryForObject and that SQL to retrieve one random greeting from the greetings table.

Additional to spring-boot-starter-jdbc we will need to add these extra dependencies:

implementation("org.postgresql:postgresql")
implementation("org.flywaydb:flyway-core")
Enter fullscreen mode Exit fullscreen mode

And the following configuration in application.yaml:

spring:
  datasource:
    url: "jdbc:postgresql://${DB_HOST:localhost}:5432/mydb"
    username: "myuser"
    password: "mypassword"
    driver-class-name: "org.postgresql.Driver"
  flyway:
    enabled: true
Enter fullscreen mode Exit fullscreen mode

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

GreetingController

We will create a GreetingController serving /hello endpoint:

@RestController
@RequestMapping("/hello")
class GreetingController(
  private val repository: GreetingRepository, 
  @Value("\${greeting.name}")
  private val name: String,
  @Value("\${greeting.secret:unknown}")
  private val secret: String
) {
  @GetMapping(produces = [MediaType.TEXT_PLAIN_VALUE])
  fun hello() 
    = "${repository.getGreeting()} my name is $name" +
        " and my secret is $secret"
}
Enter fullscreen mode Exit fullscreen mode
  • @RestController annotation will make Spring Boot to create an instance on startup and wire it properly as a REST endpoint on /hello path, scanning its annotated methods.
  • @GetMapping will map hello function answering to GET /hello requests.
  • The controller expects a GreetingRepository to be injected as well as two configuration properties, no matter what property source they come from (environment variables, system properties, configuration files, Vault, ...).
  • 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.

GreetingApplication

As a Spring Boot requirement, we need to create a main application:

@SpringBootApplication
class GreetingApplication

fun main(args: Array<String>) { 
  runApplication<GreetingApplication>(*args)
}
Enter fullscreen mode Exit fullscreen mode

By convention, all classes under the same package of the main application will be scanned for annotations.

Vault Configuration

We just add the dependency org.springframework.cloud:spring-cloud-starter-vault-config and we add the following configuration in application.yaml:

spring:
  cloud:
    vault:
      enabled: true
      uri: "http://${VAULT_HOST:localhost}:8200"
      authentication: "TOKEN"
      token: "mytoken"
      kv:
        enabled: true
        backend: "secret"
        default-context: "myapp"
        application-name: "myapp"
  config:
    import: optional:vault://
Enter fullscreen mode Exit fullscreen mode

Then we can access the configuration property greeting.secret stored in Vault.

You can check the documentation at Spring Vault.

Testing the endpoint

We can test the endpoint with a "slice test", meaning only the parts needed by the controller will be started:

@WebFluxTest
@TestPropertySource(properties = [
  "spring.cloud.vault.enabled=false",
  "greeting.secret=apple"
])
class GreetingControllerTest {

  @MockBean
  private lateinit var repository: GreetingRepository

  @Autowired
  private lateinit var client: WebTestClient

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

    client
      .get().uri("/hello")
      .exchange()
      .expectStatus().isOk
      .expectBody<String>()
      .isEqualTo(
        "Hello my name is Bitelchus and my secret is apple"
      )
    }
}
Enter fullscreen mode Exit fullscreen mode
  • We use WebTestClient to execute requests to the endpoint.
  • We mock the repository with @MockBean.
  • We can use a @TestPropertySource to configure the greeting.secret property and spring.cloud.vault.enabled=false to disable Vault.

Testing the application

To test the whole application we will use Testcontainers and the docker compose file:

@SpringBootTest(webEnvironment = RANDOM_PORT)
@Testcontainers
class GreetingApplicationTest {

  companion object {
    @Container
    private val container = DockerComposeContainer(File("../docker-compose.yaml"))
      .withServices("db", "vault", "vault-cli")
      .withLocalCompose(true)
      .waitingFor("db", forLogMessage(".*database system is ready to accept connections.*", 1))
      .waitingFor("vault", forLogMessage(".*Development mode.*", 1))
  }

  @Autowired
  private lateinit var client: WebTestClient

  @Test
  fun `should say hello`() { 
    client
      .get().uri("/hello")
      .exchange()
      .expectStatus().isOk
      .expectBody<String>().consumeWith {
        assertThat(it.responseBody!!)
         .matches(
           ".+ my name is Bitelchus and my secret is watermelon"
         )
      }
  }
}
Enter fullscreen mode Exit fullscreen mode
  • We use the shared docker compose to start the required three containers.
  • We use WebTestClient again to test the endpoint.
  • We use pattern matching to check the greeting, as it is random.
  • As Vault is now enabled, the secret should be watermelon.

Test

./gradlew test
Enter fullscreen mode Exit fullscreen mode

Run

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

# Start Application
./gradlew bootRun

# 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 fatjar and run it

# Build fatjar
./gradlew bootJar

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

# Start Application
java -jar build/libs/springboot-app-0.0.1-SNAPSHOT.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
./gradlew bootBuildImage

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

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

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

# Stop all containers
docker compose --profile springboot down
docker compose down
Enter fullscreen mode Exit fullscreen mode

That's it! Happy coding! 💙

💖 💪 🙅 🚩
rogervinas
Roger Viñas Alcon

Posted on January 15, 2023

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related