DEV Community

Cover image for πŸ₯‰ Micronaut: Top 5 Server-Side Frameworks for Kotlin in 2022
Roger ViΓ±as Alcon
Roger ViΓ±as Alcon

Posted on • Edited on

πŸ₯‰ Micronaut: 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 Micronaut implementation, you can check the other ones in this series too.

To begin with you can follow the Creating your first Micronaut application guide.

You will see that there are two alternatives:

You can also check all the other guides as well as the user documentation.

To create our sample gradle & kotlin application using the command line:

sdk install micronaut mn create-app micronaut-app \ --features=data-jdbc,postgres,flyway \ --build=gradle_kotlin --lang=kotlin --java-version=17 \ --test=junit 
Enter fullscreen mode Exit fullscreen mode

Just run it once to check everything is ok:

./gradlew run 
Enter fullscreen mode Exit fullscreen mode

And make a request to the health endpoint:

curl http://localhost:8080/health {"status":"UP"} 
Enter fullscreen mode Exit fullscreen mode

Implementation

YAML configuration

We can add to application.yaml our first configuration property:

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

We can have different "environments" active (similar to "profiles" in Spring Boot):

  • By default, no environment is enabled, so only application.yaml file will be loaded.
  • We can enable environments using MICRONAUT_ENVIRONMENTS environment variable or micronaut.environments system property.
  • When executing tests test environment is enabled.

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

More documentation about configuration sources at Application Configuration guide.

GreetingRepository

We will create a GreetingRepository:

interface GreetingRepository { fun getGreeting(): String } @Singleton open class GreetingJdbcRepository(dataSource: DataSource) : GreetingRepository { private val jdbi = Jdbi.create(dataSource) @Transactional override fun getGreeting(): String = jdbi .open() .use { it .createQuery( """ SELECT greeting FROM greetings ORDER BY random() LIMIT 1 """.trimIndent() ) .mapTo(String::class.java) .first() } } 
Enter fullscreen mode Exit fullscreen mode
  • The @Singleton annotation will make Micronaut to create an instance at startup.
  • We inject the DataSource provided by Micronaut.
  • As seems that Micronaut does not include anything similar by default, we use JDBI and that SQL to retrieve one random greeting from the greetings table.
  • We add the @Transactional annotation so Micronaut will execute the query within a database transaction. As Micronaut will instantiate a proxy class inheriting from GreetingJdbcRepository we are forced to "open" the class as all kotlin classes are final.

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

Use a specific postresql driver version (just not do depend on Micronaut BOM) and add the JDBI dependency:

implementation("org.postgresql:postgresql:42.5.1") implementation("org.jdbi:jdbi3-core:3.36.0") 
Enter fullscreen mode Exit fullscreen mode

As we added posgtresql feature when creating the project, Test Resources will magically start for us a PostgreSQL container. It is not required but we can configure a specific version of the container in application.yaml:

test-resources: containers: postgres: image-name: "postgres:14.5" 
Enter fullscreen mode Exit fullscreen mode

We should also configure the database connection for prod environment in application-prod.yaml:

datasources: default: url: "jdbc:postgresql://${DB_HOST:localhost}:5432/mydb" username: "myuser" password: "mypassword" 
Enter fullscreen mode Exit fullscreen mode

Configuring it will disable Test Resources for PostgreSQL on prod environment.

Flyway is already enabled as we created the project adding the flyway feature, so we only need to add migrations under src/main/resources/db/migration to create and populate greetings table.

GreetingController

We will create a GreetingController to serve the /hello endpoint:

@Controller("/hello") class GreetingController( private val repository: GreetingRepository, @Property(name = "greeting.name") private val name: String, @Property(name = "greeting.secret", defaultValue = "unknown") private val secret: String ) { @Get @Produces(MediaType.TEXT_PLAIN) fun hello() = "${repository.getGreeting()} 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 @Property 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.

Vault configuration

Following the HashiCorp Vault Support guide we have add this configuration to bootstrap.yaml:

micronaut: application: name: "myapp" server: port: 8080 config-client: enabled: true vault: client: config: enabled: true kv-version: "V2" secret-engine-name: "secret" test-resources: containers: postgres: image-name: "postgres:14.5" hashicorp-vault: image-name: "vault:1.12.1" path: "secret/myapp" secrets: - "greeting.secret=watermelon" 
Enter fullscreen mode Exit fullscreen mode

Note that some of this configuration was already set in application.yaml but we move it here, so it is available in the "bootstrap" phase.

Once thing not currently mentioned in the documentation is that we need to add this dependency to enable the "bootstrap" phase:

implementation("io.micronaut.discovery:micronaut-discovery-client") 
Enter fullscreen mode Exit fullscreen mode

Test Resources for Hashicorp Vault allow us to populate Vault, so for dev and test it will start a ready-to-use Vault container πŸ₯Ή

For prod environment we configure Vault in bootstrap-prod.yaml:

vault: client: uri: "http://${VAULT_HOST:localhost}:8200" token: "mytoken" 
Enter fullscreen mode Exit fullscreen mode

And as usual, configuring it will disable Test Resources for Vault on prod environment.

Testing the endpoint

We can test the endpoint this way:

@MicronautTest @Property(name = "greeting.secret", value = "apple") class GreetingControllerTest { @Inject @field:Client("/") private lateinit var client: HttpClient @Inject private lateinit var repository: GreetingRepository @Test fun `should say hello`() { every { repository.getGreeting() } returns "Hello" val request: HttpRequest<Any> = HttpRequest.GET("/hello") val response = client.toBlocking() .exchange(request, String::class.java) assertEquals(OK, response.status) assertEquals( "Hello my name is Bitelchus and my secret is apple", response.body.get() ) } @MockBean(GreetingRepository::class) fun repository() = mockk<GreetingRepository>() } 
Enter fullscreen mode Exit fullscreen mode
  • @MicronautTest will start all "Test Resources" containers, despite not needed 🀷
  • We mock the repository with @MockBean.
  • We use Micronaut's HttpClient to test the endpoint.
  • We set greeting.secret property just for this test (so we do not use the Vault value).

Testing the application

We can test the whole application this way:

@MicronautTest class GreetingApplicationTest { @Test fun `should say hello`(spec: RequestSpecification) { spec .`when`() .get("/hello") .then() .statusCode(200) .body( matchesPattern( ".+ my name is Bitelchus and my secret is watermelon" ) ) } } 
Enter fullscreen mode Exit fullscreen mode
  • @MicronautTest will start all "Test Resources" containers, now all of them are being used.
  • For this one we use Rest Assured instead of HttpClient, just to show another way. We need to add io.micronaut.test:micronaut-test-rest-assured dependency.
  • 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 ./gradlew run # Make requests curl http://localhost:8080/hello 
Enter fullscreen mode Exit fullscreen mode

Build a fatjar and run it

# Build fatjar ./gradlew shadowJar # Start Vault and Database docker compose up -d vault vault-cli db # Start Application java -Dmicronaut.environments=prod \ -jar build/libs/micronaut-app-0.1-all.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

Micronaut configures a base docker image by default but we can customize it in build.gradle.kts:

tasks.named<MicronautDockerfile>("dockerfile") { baseImage.set("eclipse-temurin:17-jre-alpine") } 
Enter fullscreen mode Exit fullscreen mode

Then:

# Build docker image ./gradlew dockerBuild # Start Vault and Database docker compose up -d vault vault-cli db # Start Application docker compose --profile micronaut up -d # Make requests curl http://localhost:8080/hello # Stop all containers docker compose --profile micronaut down docker compose down 
Enter fullscreen mode Exit fullscreen mode

Build a native executable and run it

Following Generate a Micronaut Application Native Executable with GraalVM:

# 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 ./gradlew nativeCompile # Start Vault and Database docker compose up -d vault vault-cli db # Start Application using native executable MICRONAUT_ENVIRONMENTS=prod \ ./build/native/nativeCompile/micronaut-app # Make requests curl http://localhost:8080/hello # Stop Application with control-c # Stop all containers docker compose down 
Enter fullscreen mode Exit fullscreen mode

πŸ’‘ As there were some reflection problems I had to add this reflect-config.json configuration generated with the native agent.

That's it! Happy coding! πŸ’™

Top comments (0)