DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

Related

  • Breaking Up a Monolithic Database with Kong
  • RESTful Web Services: How To Create a Context Path for Spring Boot Application or Web Service
  • Microservices With JHipster
  • Spring Boot - How To Use Native SQL Queries | Restful Web Services

Trending

  • Architecting Zero-Trust AI Agents: How to Handle Data Safely
  • Mocking Kafka for Local Spring Development
  • Why Your Test Automation Is Always Behind the Code And the Architecture That Fixes It
  • Your AI Agent Tests Are Passing, But Your Agent Is Still Broken
  1. DZone
  2. Software Design and Architecture
  3. Microservices
  4. Creating a Microservice With Quarkus, Kotlin, and Gradle

Creating a Microservice With Quarkus, Kotlin, and Gradle

A closer look at creating microservices on the modern JVM frameworks: Quarkus, Kotlin, and Gradle. Learn about the processes and technologies.

By 
Roman Kudryashov user avatar
Roman Kudryashov
·
Feb. 03, 20 · Tutorial
Likes (5)
Comment
Save
Tweet
Share
16.3K Views

Join the DZone community and get the full member experience.

Join For Free
Quarkus, Kotlin, and Gradle

Preface

In the previous article, the brief explanations of creating microservices on the modern JVM frameworks and comparison of them were shown. Now it’s time to take a closer look at the most recently appeared framework: Quarkus. I’ll describe the process of creating a microservice using the mentioned technologies and in accordance with the requirements specified in the main article.

You may also like: Build a Java REST API With Quarkus

This microservice will be a part of the following microservice architecture:

target architecture

As usual, the project’s source code is available on GitHub.

Prerequisites

  • JDK 13.

  • Consul.

Creating an Application From Scratch

To generate a new project you can use a web starter or Maven (for Maven or Gradle project generation). It is worth noting that the framework supports Java, Kotlin, and Scala languages.

Dependencies

In this project, Gradle Kotlin DSL as a build tool is used. A build script should contain:

Plugins

Listing 1. build.gradle.kts

Kotlin
 




x


 
1
plugins {
2
    kotlin("jvm")
3
    kotlin("plugin.allopen")
4
    id("io.quarkus")
5
}



Plugins' versions resolution is performed in settings.gradle.kts.

Dependencies

Listing 2. build.gradle.kts
Kotlin
 




xxxxxxxxxx
1
19


 
1
dependencies {
2
 
          
3
    ...
4
 
          
5
    implementation(enforcedPlatform("io.quarkus:quarkus-bom:$quarkusVersion"))
6
 
          
7
    implementation("io.quarkus:quarkus-resteasy-jackson")
8
 
          
9
    implementation("io.quarkus:quarkus-rest-client")
10
 
          
11
    implementation("io.quarkus:quarkus-kotlin")
12
 
          
13
    implementation("io.quarkus:quarkus-config-yaml")
14
 
          
15
    testImplementation("io.quarkus:quarkus-junit5")
16
 
          
17
    ...
18
 
          
19
}



More on importing Maven BOMs see in the Gradle docs.

Also, it is needed to make some Kotlin classes open (they’re final by default; more details on Gradle configuration in the Quarkus Kotlin guide):

Listing 3. build.gradle.kts.
Kotlin
 




xxxxxxxxxx
1


 
1
allOpen {
2
    annotation("javax.enterprise.context.ApplicationScoped")
3
}



Configuration

The framework supports configuration via properties or YAML files (more detailed in the Quarkus config guide).

The configuration file is located in the resources folder and looks like:

Listing 4. application.yaml.
YAML
 




xxxxxxxxxx
1
10


 
1
quarkus:
2
  http:
3
    host: localhost
4
    port: 8084
5
 
           
6
application-info:
7
  name: quarkus-service
8
  framework:
9
    name: Quarkus
10
    release-year: 2019



Here application’s standard properties are defined, as well as custom. Latter can be read as follows:

Listing 5. Reading properties ( source code).
Kotlin
 




xxxxxxxxxx
1
14


 
1
import io.quarkus.arc.config.ConfigProperties
2
 
           
3
@ConfigProperties(prefix = "application-info")
4
class ApplicationInfoProperties {
5
 
           
6
    lateinit var name: String
7
 
           
8
    lateinit var framework: FrameworkConfiguration
9
 
           
10
    class FrameworkConfiguration {
11
        lateinit var name: String
12
        lateinit var releaseYear: String
13
    }
14
}



Beans

Before we start with the coding part, it should be noted that there is no main method in the source code of your Quarkus application, but maybe somewhen it will be.

Injection of @ConfigProperties bean from the previous listing to another bean is performed using @Inject annotation:

Listing 6. Injection of @ConfigProperties bean ( source code)
Kotlin
 




xxxxxxxxxx
1


 
1
@ApplicationScoped
2
class ApplicationInfoService(
3
    @Inject private val applicationInfoProperties: ApplicationInfoProperties,
4
    @Inject private val serviceClient: ServiceClient
5
) {
6
    ...
7
}



ApplicationInfoService bean annotated with @ApplicationScoped can then be injected itself like this:

Listing 7. Injection of @ApplicationScoped bean ( source code)
Kotlin
 




xxxxxxxxxx
1


 
1
class ApplicationInfoResource(
2
    @Inject private val applicationInfoService: ApplicationInfoService
3
)



More on Contexts and Dependency Injection in the Quarkus CDI guide.

REST Endpoints

REST controller looks very typical for those who are familiar with Spring or Java EE:

Listing 8. REST controller ( source code).
Kotlin
 




xxxxxxxxxx
1
16


 
1
@Path("/application-info")
2
@Produces(MediaType.APPLICATION_JSON)
3
@Consumes(MediaType.APPLICATION_JSON)
4
class ApplicationInfoResource(
5
    @Inject private val applicationInfoService: ApplicationInfoService
6
) {
7
 
           
8
    @GET
9
    fun get(@QueryParam("request-to") requestTo: String?): Response =
10
        Response.ok(applicationInfoService.get(requestTo)).build()
11
 
           
12
    @GET
13
    @Path("/logo")
14
    @Produces("image/png")
15
    fun logo(): Response = Response.ok(applicationInfoService.getLogo()).build()
16
}



REST Client

For working in a microservice architecture Quarkus service should be able to perform requests to other services. Since every service has the same API, it is worth to create a uniform interface for common code, and then a bunch of REST clients extending that interface:

Listing 9. REST clients ( source code).
Kotlin
 




xxxxxxxxxx
1
23


 
1
@ApplicationScoped
2
@Path("/")
3
interface ExternalServiceClient {
4
    @GET
5
    @Path("/application-info")
6
    @Produces("application/json")
7
    fun getApplicationInfo(): ApplicationInfo
8
}
9
 
           
10
@RegisterRestClient(baseUri = "http://helidon-service")
11
interface HelidonServiceClient : ExternalServiceClient
12
 
           
13
@RegisterRestClient(baseUri = "http://ktor-service")
14
interface KtorServiceClient : ExternalServiceClient
15
 
           
16
@RegisterRestClient(baseUri = "http://micronaut-service")
17
interface MicronautServiceClient : ExternalServiceClient
18
 
           
19
@RegisterRestClient(baseUri = "http://quarkus-service")
20
interface QuarkusServiceClient : ExternalServiceClient
21
 
           
22
@RegisterRestClient(baseUri = "http://spring-boot-service")
23
interface SpringBootServiceClient : ExternalServiceClient



As you can see, creating REST clients to the other services is as simple as creating an interface using the proper JAX-RS and MicroProfile annotations.

Service Discovery

As you saw in the previous section for the baseUri parameter services' names are used. But now there is no built-in support of Service Discovery (Eureka) or it doesn’t work properly (Consul) because the framework mainly targets cloud environments. I’ve implemented Service Discovery using Consul Client for Java library. Consul client includes two necessary functions, register and getServiceInstance (which uses the Round-robin algorithm):

Listing 10. Consul client ( source code).
Kotlin
 




xxxxxxxxxx
1
32


 
1
@ApplicationScoped
2
class ConsulClient(
3
    @ConfigProperty(name = "application-info.name")
4
    private val serviceName: String,
5
    @ConfigProperty(name = "quarkus.http.port")
6
    private val port: Int
7
) {
8
 
           
9
    private val consulUrl = "http://localhost:8500"
10
    private val consulClient by lazy {
11
        Consul.builder().withUrl(consulUrl).build()
12
    }
13
    private var serviceInstanceIndex: Int = 0
14
 
           
15
    fun register() {
16
        consulClient.agentClient().register(createConsulRegistration())
17
    }
18
 
           
19
    fun getServiceInstance(serviceName: String): Service {
20
        val serviceInstances = consulClient.healthClient().getHealthyServiceInstances(serviceName).response
21
        val selectedInstance = serviceInstances[serviceInstanceIndex]
22
        serviceInstanceIndex = (serviceInstanceIndex + 1) % serviceInstances.size
23
        return selectedInstance.service
24
    }
25
 
           
26
    private fun createConsulRegistration() = ImmutableRegistration.builder()
27
        .id("$serviceName-$port")
28
        .name(serviceName)
29
        .address("localhost")
30
        .port(port)
31
        .build()
32
}



At first, it is needed to register the application:

Listing 11. Registration in Consul ( source code).
Kotlin
 




xxxxxxxxxx
1


 
1
@ApplicationScoped
2
class ConsulRegistrationBean(
3
    @Inject private val consulClient: ConsulClient
4
) {
5
 
           
6
    fun onStart(@Observes event: StartupEvent) {
7
        consulClient.register()
8
    }
9
}



Then it is needed to resolve services' names to its particular location. For that, a class that extends ClientRequestFilter and annotated with @Provider was created:

Listing 12. Filter for working with Service Discovery ( source code).
Kotlin
 




xxxxxxxxxx
1
17


 
1
@Provider
2
@ApplicationScoped
3
class ConsulFilter(
4
    @Inject private val consulClient: ConsulClient
5
) : ClientRequestFilter {
6
 
           
7
    override fun filter(requestContext: ClientRequestContext) {
8
        val serviceName = requestContext.uri.host
9
        val serviceInstance = consulClient.getServiceInstance(serviceName)
10
        val newUri: URI = URIBuilder(URI.create(requestContext.uri.toString()))
11
            .setHost(serviceInstance.address)
12
            .setPort(serviceInstance.port)
13
            .build()
14
 
           
15
        requestContext.uri = newUri
16
    }
17
}



The resolution is implemented simply by replacement URI of requestContext object with a service’s location obtained from the Consul client.

Testing

Tests for both API’s endpoints are implemented using REST Assured library:

Listing 13. Tests ( source code).
Kotlin
 




xxxxxxxxxx
1
25


 
1
@QuarkusTest
2
class QuarkusServiceApplicationTest {
3
 
           
4
    @Test
5
    fun testGet() {
6
        given()
7
            .`when`().get("/application-info")
8
            .then()
9
            .statusCode(200)
10
            .contentType(ContentType.JSON)
11
            .body("name") { `is`("quarkus-service") }
12
            .body("framework.name") { `is`("Quarkus") }
13
            .body("framework.releaseYear") { `is`(2019) }
14
    }
15
 
           
16
    @Test
17
    fun testGetLogo() {
18
        given()
19
            .`when`().get("/application-info/logo")
20
            .then()
21
            .statusCode(200)
22
            .contentType("image/png")
23
            .body(`is`(notNullValue()))
24
    }
25
}



While testing, it is not necessary to register application in Consul, so I just put ConsulClientMock that extends actual ConsulClient next to the test class:

Listing 14. Mock for ConsulClient ( source code)
Kotlin
 




xxxxxxxxxx
1


 
1
@Mock
2
@ApplicationScoped
3
class ConsulClientMock : ConsulClient("", 0) {
4
 
           
5
    // do nothing
6
    override fun register() {
7
    }
8
}



Building

During build Gradle task quarkusBuild task is being called. By default, it generates runner JAR and lib directory with all the dependencies. To produce uber-JAR artifact quarkusBuild task needs to be configured as follows:

Listing 15. Setting up the generation of uber-JAR ( source code).
Kotlin
 




xxxxxxxxxx
1


 
1
tasks {
2
    withType<QuarkusBuild> {
3
        isUberJar = true
4
    }
5
}



To build project run ./gradlew clean build in the project’s root folder.

Launch

Before launching the microservice, you need to start Consul (described in the main article).

You can start microservices:

  • Using quarkusDev Gradle task.

    Execute in the project’s root folder:

    ./gradlew :quarkus-service:quarkusDev

    Or call the task from IDE.

  • Using the uber-JAR.

    Execute in the project’s root folder:

    java -jar quarkus-service/build/quarkus-service-1.0.0-runner.jar

Now you can use REST API, for example, perform the following request:

GET http://localhost:8084/application-info

It will return:

Listing 16. API’s response.
JSON
 




xxxxxxxxxx
1


 
1
{
2
  "name": "quarkus-service",
3
  "framework": {
4
    "name": "Quarkus",
5
    "releaseYear": 2019
6
  },
7
  "requestedService": null
8
}



Spring Compatibility

The framework provides compatibility layers for several Spring technologies: DI, Web, Security, Data JPA.

Conclusion

In this article, we saw how to implement a simple REST service on Quarkus using Kotlin and Gradle. If you look at the main article, you’ll see that created application has comparable parameters to the applications on the other new JVM frameworks. The framework has serious competitors such as Helidon MicroProfile, Micronaut, and Spring Boot (if we speak about full-stack frameworks). Therefore I think that we are waiting for an interesting development of events that will be useful for the whole Java ecosystem.

P.S. Thanks to VLSI for helping with this article.


Further Reading

Thoughts on Quarkus

Building a Java App With Gradle

Kotlin (programming language) microservice Quarkus Gradle Spring Framework Listing (computer) Web Service Service discovery REST Web Protocols application

Published at DZone with permission of Roman Kudryashov. See the original article here.

Opinions expressed by DZone contributors are their own.

Related

  • Breaking Up a Monolithic Database with Kong
  • RESTful Web Services: How To Create a Context Path for Spring Boot Application or Web Service
  • Microservices With JHipster
  • Spring Boot - How To Use Native SQL Queries | Restful Web Services

Partner Resources

×

Comments

The likes didn't load as expected. Please refresh the page and try again.

  • RSS
  • X
  • Facebook

ABOUT US

  • About DZone
  • Support and feedback
  • Community research

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Core Program
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 215
  • Nashville, TN 37211
  • [email protected]

Let's be friends:

  • RSS
  • X
  • Facebook