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
⭐ 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'
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
And make a request to the endpoint:
curl http://localhost:8080/hello
Hello from RESTEasy Reactive
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
And then rename application.properties
to application.yaml
and add our first configuration property:
greeting:
name: "Bitelchus"
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()
}
- 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 randomgreeting
from thegreetings
table.
For this to work, we need some extra steps ...
Add the Reactive SQL Client extension:
quarkus extension add quarkus-reactive-pg-client
Configure it for dev
and test
profiles in application.yaml
:
quarkus:
datasource:
devservices:
image-name: "postgres:14.5"
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
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")
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
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"
}
}
- We can inject dependencies via constructor and configuration properties using
@ConfigProperty
annotation. - We expect to get
greeting.secret
from Vault, that is why we configureunknown
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
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"
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"
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"
)
)
}
}
-
@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"
)
)
}
}
-
@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
Run
# Start Application with "Dev Services"
quarkus dev
# Make requests
curl http://localhost:8080/hello
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
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
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
That's it! Happy coding! 💙
Top comments (0)