DEV Community

Cover image for 🥇 Spring Boot: Top 5 Server-Side Frameworks for Kotlin in 2022
Roger Viñas Alcon
Roger Viñas Alcon

Posted on • Edited on

🥇 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! 💙

Heroku

Build apps, not infrastructure.

Dealing with servers, hardware, and infrastructure can take up your valuable time. Discover the benefits of Heroku, the PaaS of choice for developers since 2007.

Visit Site

Top comments (0)

The Most Contextual AI Development Assistant

Pieces.app image

Our centralized storage agent works on-device, unifying various developer tools to proactively capture and enrich useful materials, streamline collaboration, and solve complex problems through a contextual understanding of your unique workflow.

👥 Ideal for solo developers, teams, and cross-company projects

Learn more

AWS Security LIVE!

Hosted by security experts, AWS Security LIVE! showcases AWS Partners tackling real-world security challenges. Join live and get your security questions answered.

Tune in to the full event

DEV is partnering to bring live events to the community. Join us or dismiss this billboard if you're not interested. ❤️