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 ๐

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

       rogervinas
       / 
        top-5-server-side-kotlin-frameworks-2022
      
        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"
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
    )!!
}
- The @Repositoryannotation will make Spring Boot to create a singleton instance at startup.
- We inject a JdbcTemplate(provided by thespring-boot-starter-jdbcautoconfiguration) to execute queries to the database.
- We use queryForObjectand that SQL to retrieve one randomgreetingfrom thegreetingstable.
Additional to spring-boot-starter-jdbc we will need to add these extra dependencies:
implementation("org.postgresql:postgresql")
implementation("org.flywaydb:flyway-core")
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
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"
}
- 
@RestControllerannotation will make Spring Boot to create an instance on startup and wire it properly as a REST endpoint on/hellopath, scanning its annotated methods.
- 
@GetMappingwill maphellofunction answering toGET /hellorequests.
- The controller expects a GreetingRepositoryto 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.secretfrom Vault, that is why we configureunknownas 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)
}
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://
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 {
  @MockitoBean
  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"
      )
    }
}
- We use WebTestClientto execute requests to the endpoint.
- We mock the repository with @MockitoBean.
- We can use a @TestPropertySourceto configure thegreeting.secretproperty andspring.cloud.vault.enabled=falseto 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"
         )
      }
  }
}
- We use the shared docker compose to start the required three containers.
- We use WebTestClientagain 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
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
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
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
That's it! Happy coding! ๐
 
 
              
 
    
Top comments (0)