<|start_filename|>ktor-service/src/main/kotlin/io/heterogeneousmicroservices/ktorservice/service/ApplicationInfoService.kt<|end_filename|>
package io.heterogeneousmicroservices.ktorservice.service

import io.heterogeneousmicroservices.ktorservice.config.ApplicationInfoProperties
import io.heterogeneousmicroservices.ktorservice.model.ApplicationInfo

class ApplicationInfoService(
    private val applicationInfoProperties: ApplicationInfoProperties,
    private val serviceClient: ServiceClient
) {

    fun get(anotherServiceName: String?): ApplicationInfo = ApplicationInfo(
        applicationInfoProperties.name,
        ApplicationInfo.Framework(
            applicationInfoProperties.frameworkName,
            applicationInfoProperties.frameworkReleaseYear
        ),
        anotherServiceName?.let { serviceClient.getApplicationInfo(it) }
    )
}

<|start_filename|>quarkus-service/src/main/kotlin/io/heterogeneousmicroservices/quarkusservice/config/ApplicationInfoProperties.kt<|end_filename|>
package io.heterogeneousmicroservices.quarkusservice.config

import io.quarkus.arc.config.ConfigProperties

@ConfigProperties(prefix = "application-info")
class ApplicationInfoProperties {

    lateinit var name: String

    lateinit var framework: FrameworkConfiguration

    class FrameworkConfiguration {
        lateinit var name: String
        lateinit var releaseYear: String
    }
}


<|start_filename|>quarkus-service/src/main/kotlin/io/heterogeneousmicroservices/quarkusservice/service/ServiceClient.kt<|end_filename|>
package io.heterogeneousmicroservices.quarkusservice.service

import io.heterogeneousmicroservices.quarkusservice.model.ApplicationInfo
import org.eclipse.microprofile.rest.client.inject.RestClient
import javax.enterprise.context.ApplicationScoped
import javax.inject.Inject

@ApplicationScoped
class ServiceClient(
    @Inject @RestClient @field: RestClient private val helidonServiceClient: HelidonServiceClient,
    @Inject @RestClient @field: RestClient private val ktorServiceClient: KtorServiceClient,
    @Inject @RestClient @field: RestClient private val micronautServiceClient: MicronautServiceClient,
    @Inject @RestClient @field: RestClient private val quarkusServiceClient: QuarkusServiceClient,
    @Inject @RestClient @field: RestClient private val springBootServiceClient: SpringBootServiceClient
) {

    companion object {
        const val helidonServiceName = "helidon-service"
        const val ktorServiceName = "ktor-service"
        const val micronautServiceName = "micronaut-service"
        const val quarkusServiceName = "quarkus-service"
        const val springBootServiceName = "spring-boot-service"
    }

    private val serviceClients = mapOf(
        helidonServiceName to helidonServiceClient,
        ktorServiceName to ktorServiceClient,
        micronautServiceName to micronautServiceClient,
        quarkusServiceName to quarkusServiceClient,
        springBootServiceName to springBootServiceClient
    )

    fun getApplicationInfo(serviceName: String): ApplicationInfo {
        val externalServiceClient =
            serviceClients[serviceName] ?: throw IllegalArgumentException("Incorrect service name: $serviceName")

        return externalServiceClient.getApplicationInfo()
    }
}




<|start_filename|>quarkus-service/src/main/kotlin/io/heterogeneousmicroservices/quarkusservice/servicediscovery/ConsulRegistrationBean.kt<|end_filename|>
package io.heterogeneousmicroservices.quarkusservice.servicediscovery

import io.quarkus.runtime.StartupEvent
import javax.enterprise.context.ApplicationScoped
import javax.enterprise.event.Observes
import javax.inject.Inject

@ApplicationScoped
class ConsulRegistrationBean(
    @Inject private val consulClient: ConsulClient
) {

    fun onStart(@Observes event: StartupEvent) {
        consulClient.register()
    }
}

<|start_filename|>micronaut-service/src/main/kotlin/io/heterogeneousmicroservices/micronautservice/service/ApplicationInfoService.kt<|end_filename|>
package io.heterogeneousmicroservices.micronautservice.service

import io.heterogeneousmicroservices.micronautservice.config.ApplicationInfoProperties
import io.heterogeneousmicroservices.micronautservice.model.ApplicationInfo
import io.micronaut.core.io.ResourceLoader
import io.micronaut.core.io.ResourceResolver
import io.micronaut.core.io.scan.ClassPathResourceLoader
import javax.inject.Singleton

@Singleton
class ApplicationInfoService(
    private val applicationInfoProperties: ApplicationInfoProperties,
    private val serviceClient: ServiceClient
) {

    fun get(anotherServiceName: String?): ApplicationInfo = ApplicationInfo(
        applicationInfoProperties.name,
        ApplicationInfo.Framework(
            applicationInfoProperties.framework.name,
            applicationInfoProperties.framework.releaseYear.toInt()
        ),
        anotherServiceName?.let { serviceClient.getApplicationInfo(it) }
    )

    fun getLogo(): ByteArray {
        val loader: ResourceLoader = ResourceResolver().getLoader(ClassPathResourceLoader::class.java).get()
        return loader.getResource("classpath:logo.png").get().readBytes()
    }
}

<|start_filename|>ktor-service/src/main/kotlin/io/heterogeneousmicroservices/ktorservice/feature/ConsulFeature.kt<|end_filename|>
package io.heterogeneousmicroservices.ktorservice.feature

import com.orbitz.consul.Consul
import io.ktor.client.HttpClient
import io.ktor.client.features.HttpClientFeature
import io.ktor.client.request.HttpRequestPipeline
import io.ktor.util.AttributeKey

class ConsulFeature(private val consulClient: Consul) {

    class Config {
        lateinit var consulClient: Consul
    }

    companion object Feature : HttpClientFeature<Config, ConsulFeature> {
        var serviceInstanceIndex: Int = 0

        override val key = AttributeKey<ConsulFeature>("ConsulFeature")

        override fun prepare(block: Config.() -> Unit) = ConsulFeature(Config().apply(block).consulClient)

        override fun install(feature: ConsulFeature, scope: HttpClient) {
            scope.requestPipeline.intercept(HttpRequestPipeline.Render) {
                val serviceName = context.url.host
                val serviceInstances =
                    feature.consulClient.healthClient().getHealthyServiceInstances(serviceName).response
                val selectedInstance = serviceInstances[serviceInstanceIndex]
                context.url.apply {
                    host = selectedInstance.service.address
                    port = selectedInstance.service.port
                }
                serviceInstanceIndex = (serviceInstanceIndex + 1) % serviceInstances.size
            }
        }
    }
}

<|start_filename|>spring-boot-service/src/main/kotlin/io/heterogeneousmicroservices/springbootservice/config/ApplicationInfoProperties.kt<|end_filename|>
package io.heterogeneousmicroservices.springbootservice.config

import org.springframework.boot.context.properties.ConfigurationProperties
import org.springframework.stereotype.Component

@Component
@ConfigurationProperties("application-info")
class ApplicationInfoProperties {

    lateinit var name: String
    val framework = FrameworkConfigurationProperties()

    class FrameworkConfigurationProperties {
        lateinit var name: String
        lateinit var releaseYear: String
    }
}

<|start_filename|>micronaut-service/src/main/kotlin/io/heterogeneousmicroservices/micronautservice/MicronautServiceApplication.kt<|end_filename|>
package io.heterogeneousmicroservices.micronautservice

import io.micronaut.runtime.Micronaut

object MicronautServiceApplication {

    @JvmStatic
    fun main(args: Array<String>) {
        Micronaut.build()
            .packages("io.heterogeneousmicroservices.micronautservice")
            .mainClass(MicronautServiceApplication.javaClass)
            .start()
    }
}

<|start_filename|>quarkus-service/src/main/kotlin/io/heterogeneousmicroservices/quarkusservice/service/ApplicationInfoService.kt<|end_filename|>
package io.heterogeneousmicroservices.quarkusservice.service

import io.heterogeneousmicroservices.quarkusservice.config.ApplicationInfoProperties
import io.heterogeneousmicroservices.quarkusservice.model.ApplicationInfo
import javax.enterprise.context.ApplicationScoped
import javax.inject.Inject

@ApplicationScoped
class ApplicationInfoService(
    @Inject private val applicationInfoProperties: ApplicationInfoProperties,
    @Inject private val serviceClient: ServiceClient
) {

    fun get(anotherServiceName: String?): ApplicationInfo = ApplicationInfo(
        applicationInfoProperties.name,
        ApplicationInfo.Framework(
            applicationInfoProperties.framework.name,
            applicationInfoProperties.framework.releaseYear.toInt()
        ),
        anotherServiceName?.let { serviceClient.getApplicationInfo(it) }
    )

    fun getLogo(): ByteArray = javaClass.classLoader.getResourceAsStream("logo.png")?.readBytes()
        ?: throw RuntimeException("Can't find logo")
}

<|start_filename|>ktor-service/src/main/kotlin/io/heterogeneousmicroservices/ktorservice/KtorServiceApplication.kt<|end_filename|>
package io.heterogeneousmicroservices.ktorservice

import com.orbitz.consul.Consul
import io.heterogeneousmicroservices.ktorservice.config.ApplicationInfoProperties
import io.heterogeneousmicroservices.ktorservice.service.ApplicationInfoService
import io.heterogeneousmicroservices.ktorservice.service.ServiceClient
import io.ktor.server.engine.commandLineEnvironment
import io.ktor.server.engine.embeddedServer
import io.ktor.server.netty.Netty
import org.koin.core.context.startKoin
import org.koin.dsl.module

val koinModule = module {
    single { ApplicationInfoService(get(), get()) }
    single { ApplicationInfoProperties() }
    single { ServiceClient(get()) }
    single { Consul.builder().withUrl("http://localhost:8500").build() }
}

fun main(args: Array<String>) {
    startKoin {
        modules(koinModule)
    }
    val server = embeddedServer(Netty, commandLineEnvironment(args))
    server.start(wait = true)
}

<|start_filename|>quarkus-service/src/test/kotlin/io/heterogeneousmicroservices/quarkusservice/ConsulClientMock.kt<|end_filename|>
package io.heterogeneousmicroservices.quarkusservice

import io.heterogeneousmicroservices.quarkusservice.servicediscovery.ConsulClient
import io.quarkus.test.Mock
import javax.enterprise.context.ApplicationScoped

@Mock
@ApplicationScoped
class ConsulClientMock : ConsulClient("", 0) {

    // do nothing
    override fun register() {
    }
}

<|start_filename|>quarkus-service/src/main/kotlin/io/heterogeneousmicroservices/quarkusservice/servicediscovery/ConsulFilter.kt<|end_filename|>
package io.heterogeneousmicroservices.quarkusservice.servicediscovery

import org.apache.http.client.utils.URIBuilder
import javax.enterprise.context.ApplicationScoped
import javax.inject.Inject
import javax.ws.rs.client.ClientRequestContext
import javax.ws.rs.client.ClientRequestFilter
import javax.ws.rs.ext.Provider

@Provider
@ApplicationScoped
class ConsulFilter(
    @Inject private val consulClient: ConsulClient
) : ClientRequestFilter {

    override fun filter(requestContext: ClientRequestContext) {
        val serviceName = requestContext.uri.host
        val serviceInstance = consulClient.getServiceInstance(serviceName)
        val newUri = URIBuilder(requestContext.uri)
            .setHost(serviceInstance.address)
            .setPort(serviceInstance.port)
            .build()

        requestContext.uri = newUri
    }
}


<|start_filename|>micronaut-service/src/main/kotlin/io/heterogeneousmicroservices/micronautservice/service/ServiceClient.kt<|end_filename|>
package io.heterogeneousmicroservices.micronautservice.service

import io.heterogeneousmicroservices.micronautservice.model.ApplicationInfo
import io.micronaut.http.HttpRequest
import io.micronaut.http.client.RxHttpClient
import io.micronaut.http.client.annotation.Client
import javax.inject.Inject
import javax.inject.Singleton

@Singleton
class ServiceClient(
    @Client(helidonServiceName) @Inject private val helidonServiceClient: RxHttpClient,
    @Client(ktorServiceName) @Inject private val ktorServiceClient: RxHttpClient,
    @Client(micronautServiceName) @Inject private val micronautServiceClient: RxHttpClient,
    @Client(quarkusServiceName) @Inject private val quarkusServiceClient: RxHttpClient,
    @Client(springBootServiceName) @Inject private val springBootServiceClient: RxHttpClient
) {

    companion object {
        const val helidonServiceName = "helidon-service"
        const val ktorServiceName = "ktor-service"
        const val micronautServiceName = "micronaut-service"
        const val quarkusServiceName = "quarkus-service"
        const val springBootServiceName = "spring-boot-service"
    }

    private val serviceClients = mapOf(
        helidonServiceName to helidonServiceClient,
        ktorServiceName to ktorServiceClient,
        micronautServiceName to micronautServiceClient,
        quarkusServiceName to quarkusServiceClient,
        springBootServiceName to springBootServiceClient
    )

    fun getApplicationInfo(serviceName: String): ApplicationInfo {
        val httpClient =
            serviceClients[serviceName] ?: throw IllegalArgumentException("Incorrect service name: $serviceName")
        val request = HttpRequest.GET<ApplicationInfo>("/application-info")
        return httpClient.retrieve(request, ApplicationInfo::class.java).blockingFirst()
    }
}

<|start_filename|>helidon-service/src/main/kotlin/io/heterogeneousmicroservices/helidonservice/service/ApplicationInfoService.kt<|end_filename|>
package io.heterogeneousmicroservices.helidonservice.service

import io.helidon.common.configurable.Resource
import io.heterogeneousmicroservices.helidonservice.config.ApplicationInfoProperties
import io.heterogeneousmicroservices.helidonservice.model.ApplicationInfo

class ApplicationInfoService(
    private val applicationInfoProperties: ApplicationInfoProperties,
    private val serviceClient: ServiceClient
) {

    fun get(anotherServiceName: String?): ApplicationInfo = ApplicationInfo(
        applicationInfoProperties.name,
        ApplicationInfo.Framework(
            applicationInfoProperties.frameworkName,
            applicationInfoProperties.frameworkReleaseYear
        ),
        anotherServiceName?.let { serviceClient.getApplicationInfo(it) }
    )

    fun getLogo(): ByteArray = Resource.create("logo.png").bytes()
}

<|start_filename|>micronaut-service/src/main/kotlin/io/heterogeneousmicroservices/micronautservice/config/ApplicationInfoProperties.kt<|end_filename|>
package io.heterogeneousmicroservices.micronautservice.config

import io.micronaut.context.annotation.ConfigurationProperties

@ConfigurationProperties("application-info")
class ApplicationInfoProperties {

    lateinit var name: String
    lateinit var framework: FrameworkConfigurationProperties

    @ConfigurationProperties("framework")
    class FrameworkConfigurationProperties {
        lateinit var name: String
        lateinit var releaseYear: String
    }
}

<|start_filename|>spring-boot-service/src/main/kotlin/io/heterogeneousmicroservices/springbootservice/web/ApplicationInfoController.kt<|end_filename|>
package io.heterogeneousmicroservices.springbootservice.web

import io.heterogeneousmicroservices.springbootservice.model.ApplicationInfo
import io.heterogeneousmicroservices.springbootservice.service.ApplicationInfoService
import org.springframework.http.MediaType
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RequestParam
import org.springframework.web.bind.annotation.RestController

@RestController
@RequestMapping(path = ["application-info"], produces = [MediaType.APPLICATION_JSON_VALUE])
class ApplicationInfoController(
    private val applicationInfoService: ApplicationInfoService
) {

    @GetMapping
    fun get(@RequestParam("request-to") requestTo: String?): ApplicationInfo = applicationInfoService.get(requestTo)

    @GetMapping(path = ["/logo"], produces = [MediaType.IMAGE_PNG_VALUE])
    fun getLogo(): ByteArray = applicationInfoService.getLogo()
}

<|start_filename|>ktor-service/src/main/kotlin/io/heterogeneousmicroservices/ktorservice/module/KtorServiceApplicationModule.kt<|end_filename|>
package io.heterogeneousmicroservices.ktorservice.module

import com.orbitz.consul.Consul
import com.orbitz.consul.model.agent.ImmutableRegistration
import com.typesafe.config.ConfigFactory
import com.typesafe.config.ConfigUtil
import io.heterogeneousmicroservices.ktorservice.service.ApplicationInfoService
import io.ktor.application.Application
import io.ktor.application.call
import io.ktor.application.install
import io.ktor.config.ApplicationConfig
import io.ktor.config.HoconApplicationConfig
import io.ktor.features.CallLogging
import io.ktor.features.Compression
import io.ktor.features.ContentNegotiation
import io.ktor.features.DefaultHeaders
import io.ktor.http.content.resource
import io.ktor.http.content.static
import io.ktor.jackson.jackson
import io.ktor.response.respond
import io.ktor.routing.get
import io.ktor.routing.route
import io.ktor.routing.routing
import org.koin.ktor.ext.inject

private val ktorDeploymentConfig: ApplicationConfig =
    HoconApplicationConfig(ConfigFactory.load().getConfig(ConfigUtil.joinPath("ktor", "deployment")))
private val port: Int = ktorDeploymentConfig.property("port").getString().toInt()

// referenced in application.conf
fun Application.module() {
    val applicationInfoService: ApplicationInfoService by inject()

    if (!isTest()) {
        val consulClient: Consul by inject()
        registerInConsul(applicationInfoService.get(null).name, consulClient)
    }

    install(DefaultHeaders)
    install(Compression)
    install(CallLogging)
    install(ContentNegotiation) {
        jackson {}
    }

    routing {
        route("application-info") {
            get {
                val requestTo: String? = call.parameters["request-to"]
                call.respond(applicationInfoService.get(requestTo))
            }
            static {
                resource("/logo", "logo.png")
            }
        }
    }
}

private fun registerInConsul(serviceName: String, consulClient: Consul) =
    consulClient.agentClient().register(buildConsulRegistration(serviceName))

private fun buildConsulRegistration(serviceName: String) = ImmutableRegistration.builder()
    .id("$serviceName-$port")
    .name(serviceName)
    .address("localhost")
    .port(port)
    .build()

private fun Application.isTest(): Boolean =
    environment.config.property("ktor.deployment.environment").getString() == "test"

<|start_filename|>spring-boot-service/src/main/kotlin/io/heterogeneousmicroservices/springbootservice/service/ApplicationInfoService.kt<|end_filename|>
package io.heterogeneousmicroservices.springbootservice.service

import io.heterogeneousmicroservices.springbootservice.config.ApplicationInfoProperties
import io.heterogeneousmicroservices.springbootservice.model.ApplicationInfo
import org.springframework.core.io.ClassPathResource
import org.springframework.stereotype.Service

@Service
class ApplicationInfoService(
    private val applicationInfoProperties: ApplicationInfoProperties,
    private val serviceClient: ServiceClient
) {

    fun get(anotherServiceName: String?): ApplicationInfo = ApplicationInfo(
        applicationInfoProperties.name,
        ApplicationInfo.Framework(
            applicationInfoProperties.framework.name,
            applicationInfoProperties.framework.releaseYear.toInt()
        ),
        anotherServiceName?.let { serviceClient.getApplicationInfo(it) }
    )

    fun getLogo(): ByteArray = ClassPathResource("logo.png").inputStream.readBytes()
}

<|start_filename|>helidon-service/src/main/kotlin/io/heterogeneousmicroservices/helidonservice/model/ApplicationInfo.kt<|end_filename|>
package io.heterogeneousmicroservices.helidonservice.model

data class ApplicationInfo(
    val name: String,
    val framework: Framework,
    val requestedService: ApplicationInfo?
) {

    data class Framework(
        val name: String,
        val releaseYear: Int
    )
}

<|start_filename|>quarkus-service/build.gradle.kts<|end_filename|>
import io.quarkus.gradle.tasks.QuarkusBuild
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile

version = "1.0.0"

val quarkusVersion: String by project
val consulClientVersion: String by project

plugins {
    kotlin("jvm")
    kotlin("plugin.allopen")
    id("io.quarkus")
    jacoco
}

dependencies {
    implementation(kotlin("stdlib"))
    implementation(enforcedPlatform("io.quarkus:quarkus-bom:$quarkusVersion"))
    implementation("io.quarkus:quarkus-resteasy-jackson")
    implementation("io.quarkus:quarkus-rest-client")
    implementation("io.quarkus:quarkus-kotlin")
    implementation("io.quarkus:quarkus-config-yaml")
    // todo use native service Service Discovery support (https://github.com/quarkusio/quarkus/issues/5812)
//    implementation("org.microprofile-ext.config-ext:configsource-consul:1.0.9")
    implementation("com.fasterxml.jackson.module:jackson-module-kotlin")
    implementation("com.orbitz.consul:consul-client:$consulClientVersion")
    testImplementation("io.quarkus:quarkus-junit5")
    testImplementation("io.rest-assured:rest-assured")
}

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

tasks {
    withType<QuarkusBuild> {
        isUberJar = true
    }
    withType<Test> {
        useJUnitPlatform()
    }
    withType<KotlinCompile> {
        kotlinOptions {
            jvmTarget = "11"
            freeCompilerArgs = listOf("-Xjsr305=strict")
        }
    }
}


<|start_filename|>helidon-service/src/main/kotlin/io/heterogeneousmicroservices/helidonservice/config/ApplicationInfoProperties.kt<|end_filename|>
package io.heterogeneousmicroservices.helidonservice.config

import io.helidon.config.Config
import io.heterogeneousmicroservices.helidonservice.model.ApplicationInfo

class ApplicationInfoProperties(

    private val applicationInfo: ApplicationInfo = Config.create()
        .get("application-info").`as` {
            ApplicationInfo(
                it["name"].asString().get(),
                ApplicationInfo.Framework(
                    it["framework"]["name"].asString().get(),
                    it["framework"]["release-year"].asInt().get()
                ),
                null
            )
        }.orElseThrow { IllegalStateException("Cannot parse config") },

    val name: String = applicationInfo.name,
    val frameworkName: String = applicationInfo.framework.name,
    val frameworkReleaseYear: Int = applicationInfo.framework.releaseYear
)

<|start_filename|>spring-boot-service/src/main/kotlin/io/heterogeneousmicroservices/springbootservice/SpringBootServiceApplication.kt<|end_filename|>
package io.heterogeneousmicroservices.springbootservice

import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.runApplication
import org.springframework.cloud.client.loadbalancer.LoadBalanced
import org.springframework.context.annotation.Bean
import org.springframework.web.client.RestTemplate

@SpringBootApplication
class SpringBootServiceApplication {
    @Bean
    @LoadBalanced
    fun restTemplate() = RestTemplate()

    @Bean
    fun objectMapper() = jacksonObjectMapper()
}

fun main(args: Array<String>) {
    runApplication<SpringBootServiceApplication>(*args)
}


<|start_filename|>quarkus-service/src/main/kotlin/io/heterogeneousmicroservices/quarkusservice/servicediscovery/ConsulClient.kt<|end_filename|>
package io.heterogeneousmicroservices.quarkusservice.servicediscovery

import com.orbitz.consul.Consul
import com.orbitz.consul.model.agent.ImmutableRegistration
import com.orbitz.consul.model.health.Service
import org.eclipse.microprofile.config.inject.ConfigProperty
import javax.enterprise.context.ApplicationScoped

@ApplicationScoped
class ConsulClient(
    @ConfigProperty(name = "application-info.name")
    private val serviceName: String,
    @ConfigProperty(name = "quarkus.http.port")
    private val port: Int
) {

    private val consulUrl = "http://localhost:8500"
    private val consulClient by lazy {
        Consul.builder().withUrl(consulUrl).build()
    }
    private var serviceInstanceIndex: Int = 0

    fun register() {
        consulClient.agentClient().register(createConsulRegistration())
    }

    fun getServiceInstance(serviceName: String): Service {
        val serviceInstances = consulClient.healthClient().getHealthyServiceInstances(serviceName).response
        val selectedInstance = serviceInstances[serviceInstanceIndex]
        serviceInstanceIndex = (serviceInstanceIndex + 1) % serviceInstances.size
        return selectedInstance.service
    }

    private fun createConsulRegistration() = ImmutableRegistration.builder()
        .id("$serviceName-$port")
        .name(serviceName)
        .address("localhost")
        .port(port)
        .build()
}

<|start_filename|>helidon-service/build.gradle.kts<|end_filename|>
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile

val helidonVersion: String by project
val koinVersion: String by project
val consulClientVersion: String by project
val jacksonVersion: String by project
val logbackVersion: String by project
val junitVersion: String by project
val mockitoVersion: String by project

plugins {
    application
    id("com.github.johnrengelman.shadow")
    kotlin("jvm")
    jacoco
}

application {
    mainClassName = "io.heterogeneousmicroservices.helidonservice.HelidonServiceApplication"
}

dependencies {
    implementation(kotlin("stdlib"))
    implementation(enforcedPlatform("io.helidon:helidon-bom:$helidonVersion"))
    implementation("io.helidon.bundles:helidon-bundles-webserver")
    implementation("io.helidon.media.jackson:helidon-media-jackson-common")
    implementation("io.helidon.bundles:helidon-bundles-config")
    implementation("org.koin:koin-core:$koinVersion")
    implementation("com.orbitz.consul:consul-client:$consulClientVersion")
    implementation("com.fasterxml.jackson.module:jackson-module-kotlin:$jacksonVersion")
    runtimeOnly("ch.qos.logback:logback-classic:$logbackVersion")
    testImplementation("org.koin:koin-test:$koinVersion")
    testImplementation("org.junit.jupiter:junit-jupiter:$junitVersion")
    // fixme temporary override mockito version that comes from koin-test
    testRuntimeOnly("org.mockito:mockito-core:$mockitoVersion")
}

tasks {
    withType<Test> {
        useJUnitPlatform()
    }
    withType<KotlinCompile> {
        kotlinOptions {
            jvmTarget = "11"
            freeCompilerArgs = listOf("-Xjsr305=strict")
        }
    }
}


<|start_filename|>settings.gradle.kts<|end_filename|>
rootProject.name = "heterogeneous-microservices"

include(
    "helidon-service",
    "ktor-service",
    "micronaut-service",
    "quarkus-service",
    "spring-boot-service"
)

pluginManagement {
    plugins {
        fun String.getVersion() = extra["$this.version"].toString()
        fun PluginDependenciesSpec.resolve(id: String, versionKey: String = id) = id(id) version versionKey.getVersion()

        resolve("org.jetbrains.kotlin.jvm")
        resolve("org.jetbrains.kotlin.kapt", "org.jetbrains.kotlin.jvm")
        resolve("org.jetbrains.kotlin.plugin.spring", "org.jetbrains.kotlin.jvm")
        resolve("org.jetbrains.kotlin.plugin.allopen", "org.jetbrains.kotlin.jvm")
        resolve("com.github.johnrengelman.shadow")
        resolve("io.quarkus")
        resolve("org.springframework.boot")
    }
}


<|start_filename|>_misc/api_testing/idea_http_client/scripts/check-status-script.js<|end_filename|>
client.test("Request executed successfully", function () {
    client.assert(response.status === 200, "Response status is not 200");
});

<|start_filename|>ktor-service/build.gradle.kts<|end_filename|>
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile

val ktorVersion: String by project
val koinVersion: String by project
val consulClientVersion: String by project
val logbackVersion: String by project
val junitVersion: String by project
val mockitoVersion: String by project

plugins {
    application
    id("com.github.johnrengelman.shadow")
    kotlin("jvm")
    jacoco
}

application {
    mainClassName = "io.heterogeneousmicroservices.ktorservice.KtorServiceApplicationKt"
//    mainClassName = "io.ktor.server.netty.EngineMain"
}

dependencies {
    implementation(kotlin("stdlib"))
    implementation("io.ktor:ktor-server-netty:$ktorVersion")
    implementation("io.ktor:ktor-jackson:$ktorVersion")
    implementation("io.ktor:ktor-client-apache:$ktorVersion")
    implementation("io.ktor:ktor-client-jackson:$ktorVersion")
    implementation("org.koin:koin-ktor:$koinVersion")
    implementation("com.orbitz.consul:consul-client:$consulClientVersion")
    runtimeOnly("ch.qos.logback:logback-classic:$logbackVersion")
    testImplementation("io.ktor:ktor-server-test-host:$ktorVersion")
    testImplementation("org.koin:koin-test:$koinVersion")
    testImplementation("org.junit.jupiter:junit-jupiter:$junitVersion")
    // fixme temporary override mockito version that comes from koin-test
    testRuntimeOnly("org.mockito:mockito-core:$mockitoVersion")
}

tasks {
    withType<Test> {
        useJUnitPlatform()
    }
    withType<KotlinCompile> {
        kotlinOptions {
            jvmTarget = "11"
            freeCompilerArgs = listOf("-Xjsr305=strict")
        }
    }
}


<|start_filename|>micronaut-service/src/test/kotlin/io/heterogeneousmicroservices/micronautservice/MicronautServiceApplicationTest.kt<|end_filename|>
package io.heterogeneousmicroservices.micronautservice

import io.heterogeneousmicroservices.micronautservice.model.ApplicationInfo
import io.micronaut.core.io.ResourceLoader
import io.micronaut.core.io.ResourceResolver
import io.micronaut.core.io.scan.ClassPathResourceLoader
import io.micronaut.http.HttpResponse
import io.micronaut.http.HttpStatus
import io.micronaut.http.MediaType
import io.micronaut.http.client.HttpClient
import io.micronaut.http.client.annotation.Client
import io.micronaut.test.annotation.MicronautTest
import org.junit.jupiter.api.Assertions.assertArrayEquals
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Test
import javax.inject.Inject

@MicronautTest
class MicronautServiceApplicationTest {

    @Inject
    @field: Client("/")
    private lateinit var client: HttpClient

    @Test
    fun testGet() {
        val response: HttpResponse<ApplicationInfo> =
            client.toBlocking().exchange("/application-info", ApplicationInfo::class.java)

        assertEquals(HttpStatus.OK, response.status)
        assertEquals(MediaType.APPLICATION_JSON, response.contentType.get().name)
        val expected = ApplicationInfo("micronaut-service", ApplicationInfo.Framework("Micronaut", 2018), null)
        assertEquals(expected, response.body.get())
    }

    @Test
    fun testGetLogo() {
        val response: HttpResponse<ByteArray> =
            client.toBlocking().exchange("/application-info/logo", ByteArray::class.java)

        assertEquals(HttpStatus.OK, response.status)
        assertEquals(MediaType.IMAGE_PNG, response.contentType.get().name)
        val loader: ResourceLoader = ResourceResolver().getLoader(ClassPathResourceLoader::class.java).get()
        val expected = loader.getResource("classpath:logo.png").get().readBytes()
        assertArrayEquals(expected, response.body.get())
    }
}


<|start_filename|>helidon-service/src/main/kotlin/io/heterogeneousmicroservices/helidonservice/service/ServiceClient.kt<|end_filename|>
package io.heterogeneousmicroservices.helidonservice.service

import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
import com.fasterxml.jackson.module.kotlin.readValue
import com.orbitz.consul.Consul
import com.orbitz.consul.model.health.Service
import io.heterogeneousmicroservices.helidonservice.model.ApplicationInfo
import java.net.URI
import java.net.http.HttpClient
import java.net.http.HttpRequest
import java.net.http.HttpResponse.BodyHandlers

class ServiceClient(
    private val consulClient: Consul
) {

    private val httpClient = HttpClient.newHttpClient()
    private val objectMapper = jacksonObjectMapper()
    private var serviceInstanceIndex = 0

    fun getApplicationInfo(serviceName: String): ApplicationInfo {
        val serviceInstance = getNext(serviceName)
        val serviceUrl = "http://${serviceInstance.address}:${serviceInstance.port}"
        val request = HttpRequest.newBuilder()
            .uri(URI.create("$serviceUrl/application-info"))
            .build()
        val response = httpClient.send(request, BodyHandlers.ofString())
        return objectMapper.readValue(response.body())
    }

    private fun getNext(serviceName: String): Service {
        val serviceInstances = consulClient.healthClient().getHealthyServiceInstances(serviceName).response
        val selectedInstance = serviceInstances[serviceInstanceIndex]
        serviceInstanceIndex = (serviceInstanceIndex + 1) % serviceInstances.size
        return selectedInstance.service
    }
}

<|start_filename|>quarkus-service/src/test/kotlin/io/heterogeneousmicroservices/quarkusservice/QuarkusServiceApplicationTest.kt<|end_filename|>
package io.heterogeneousmicroservices.quarkusservice

import io.quarkus.test.junit.QuarkusTest
import io.restassured.RestAssured.given
import io.restassured.http.ContentType
import org.hamcrest.CoreMatchers.`is`
import org.hamcrest.CoreMatchers.notNullValue
import org.junit.jupiter.api.Test

@QuarkusTest
class QuarkusServiceApplicationTest {

    @Test
    fun testGet() {
        given()
            .`when`().get("/application-info")
            .then()
            .statusCode(200)
            .contentType(ContentType.JSON)
            .body("name") { `is`("quarkus-service") }
            .body("framework.name") { `is`("Quarkus") }
            .body("framework.releaseYear") { `is`(2019) }
    }

    @Test
    fun testGetLogo() {
        given()
            .`when`().get("/application-info/logo")
            .then()
            .statusCode(200)
            .contentType("image/png")
            .body(`is`(notNullValue()))
    }
}

<|start_filename|>micronaut-service/src/main/kotlin/io/heterogeneousmicroservices/micronautservice/web/ApplicationInfoController.kt<|end_filename|>
package io.heterogeneousmicroservices.micronautservice.web

import io.heterogeneousmicroservices.micronautservice.model.ApplicationInfo
import io.heterogeneousmicroservices.micronautservice.service.ApplicationInfoService
import io.micronaut.http.MediaType
import io.micronaut.http.annotation.Controller
import io.micronaut.http.annotation.Get

@Controller(
    value = "/application-info",
    consumes = [MediaType.APPLICATION_JSON],
    produces = [MediaType.APPLICATION_JSON]
)
class ApplicationInfoController(
    private val applicationInfoService: ApplicationInfoService
) {

    @Get
    fun get(requestTo: String?): ApplicationInfo = applicationInfoService.get(requestTo)

    @Get("/logo", produces = [MediaType.IMAGE_PNG])
    fun getLogo(): ByteArray = applicationInfoService.getLogo()
}

<|start_filename|>spring-boot-service/src/main/kotlin/io/heterogeneousmicroservices/springbootservice/service/ServiceClient.kt<|end_filename|>
package io.heterogeneousmicroservices.springbootservice.service

import io.heterogeneousmicroservices.springbootservice.model.ApplicationInfo
import org.springframework.stereotype.Service
import org.springframework.web.client.RestTemplate

@Service
class ServiceClient(
    private val restTemplate: RestTemplate
) {

    fun getApplicationInfo(serviceName: String): ApplicationInfo = restTemplate
        .getForEntity("http://$serviceName/application-info", ApplicationInfo::class.java)
        .body ?: throw IllegalStateException("No result")
}

<|start_filename|>spring-boot-service/src/test/kotlin/io/heterogeneousmicroservices/springbootservice/SpringBootServiceApplicationTest.kt<|end_filename|>
package io.heterogeneousmicroservices.springbootservice

import io.heterogeneousmicroservices.springbootservice.model.ApplicationInfo
import org.hamcrest.MatcherAssert
import org.hamcrest.Matchers
import org.junit.jupiter.api.Assertions.assertArrayEquals
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.extension.ExtendWith
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.context.SpringBootTest
import org.springframework.core.io.ClassPathResource
import org.springframework.http.MediaType
import org.springframework.test.context.junit.jupiter.SpringExtension
import org.springframework.test.web.reactive.server.WebTestClient

@ExtendWith(SpringExtension::class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
class SpringBootServiceApplicationTest {

    @Autowired
    private lateinit var webTestClient: WebTestClient

    @Test
    fun testGet() {
        webTestClient
            .get().uri("/application-info")
            .exchange()
            .expectHeader().contentType(MediaType.APPLICATION_JSON)
            .expectStatus().isOk
            .expectBody(ApplicationInfo::class.java)
            .returnResult().apply {
                MatcherAssert.assertThat(
                    this.responseBody, Matchers.equalTo(
                        ApplicationInfo(
                            "spring-boot-service",
                            ApplicationInfo.Framework("Spring Boot", 2014),
                            null
                        )
                    )
                )
            }
    }

    @Test
    fun testGetLogo() {
        webTestClient
            .get().uri("/application-info/logo")
            .exchange()
            .expectHeader().contentType(MediaType.IMAGE_PNG_VALUE)
            .expectStatus().isOk
            .expectBody(ByteArray::class.java)
            .returnResult().apply {
                assertArrayEquals(ClassPathResource("logo.png").inputStream.readBytes(), this.responseBody)
            }
    }
}

<|start_filename|>helidon-service/src/test/kotlin/io/heterogeneousmicroservices/helidonservice/HelidonServiceApplicationTest.kt<|end_filename|>
package io.heterogeneousmicroservices.helidonservice

import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
import com.orbitz.consul.Consul
import io.helidon.common.configurable.Resource
import io.helidon.common.http.MediaType
import io.helidon.webserver.WebServer
import io.heterogeneousmicroservices.helidonservice.config.ApplicationInfoProperties
import io.heterogeneousmicroservices.helidonservice.model.ApplicationInfo
import io.heterogeneousmicroservices.helidonservice.service.ApplicationInfoService
import org.junit.jupiter.api.AfterEach
import org.junit.jupiter.api.Assertions.assertArrayEquals
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
import org.koin.core.context.startKoin
import org.koin.core.context.stopKoin
import org.koin.core.inject
import org.koin.test.AutoCloseKoinTest
import org.koin.test.mock.declareMock
import java.net.HttpURLConnection
import java.net.URL
import java.util.concurrent.TimeUnit

class HelidonServiceApplicationTest : AutoCloseKoinTest() {

    private var server: WebServer? = null

    // todo how to start server and koin once?
    @BeforeEach
    fun beforeEach() {
        startKoin {
            modules(koinModule)
        }
        this.declareMock<Consul>()
        val applicationInfoService: ApplicationInfoService by inject()
        val consulClient: Consul by inject()
        val applicationInfoProperties: ApplicationInfoProperties by inject()

        val startTimeout = 30000L // 30 seconds should be enough
        val startTime = System.currentTimeMillis()

        server = startServer(applicationInfoService, consulClient, applicationInfoProperties.name, startTime)
        server?.let {
            while (!it.isRunning) {
                Thread.sleep(100)
                check(System.currentTimeMillis() - startTime <= startTimeout) { "Webserver haven't been started" }
            }
        }
    }

    @AfterEach
    fun afterEach() {
        server?.shutdown()
            ?.toCompletableFuture()
            ?.get(5, TimeUnit.SECONDS)
        stopKoin()
    }

    @Test
    fun testGet() {
        val connection = getURLConnection("GET", "/application-info")
        assertEquals(200, connection.responseCode)
        assertEquals(MediaType.APPLICATION_JSON.toString(), connection.contentType)

        val expected = ApplicationInfo("helidon-service", ApplicationInfo.Framework("Helidon SE", 2019), null)
        val actual = jacksonObjectMapper().readValue(connection.inputStream, ApplicationInfo::class.java)
        assertEquals(expected, actual)
    }

    @Test
    fun testGetLogo() {
        val connection = getURLConnection("GET", "/application-info/logo")
        assertEquals(200, connection.responseCode)
        assertEquals(MediaType.create("image", "png").toString(), connection.contentType)
        assertArrayEquals(Resource.create("logo.png").bytes(), connection.inputStream.readBytes())
    }

    private fun getURLConnection(method: String, path: String): HttpURLConnection {
        val url = URL("http://localhost:" + server?.port() + path)
        return (url.openConnection() as HttpURLConnection).apply {
            this.requestMethod = method
            this.setRequestProperty("Accept", "application/json")
        }
    }
}

<|start_filename|>helidon-service/src/main/kotlin/io/heterogeneousmicroservices/helidonservice/HelidonServiceApplication.kt<|end_filename|>
package io.heterogeneousmicroservices.helidonservice

import com.orbitz.consul.Consul
import com.orbitz.consul.model.agent.ImmutableRegistration
import io.helidon.common.http.Http
import io.helidon.common.http.MediaType
import io.helidon.config.Config
import io.helidon.media.jackson.common.JacksonSupport
import io.helidon.webserver.Handler
import io.helidon.webserver.Routing
import io.helidon.webserver.ServerConfiguration
import io.helidon.webserver.WebServer
import io.heterogeneousmicroservices.helidonservice.config.ApplicationInfoProperties
import io.heterogeneousmicroservices.helidonservice.service.ApplicationInfoService
import io.heterogeneousmicroservices.helidonservice.service.ServiceClient
import org.koin.core.KoinComponent
import org.koin.core.context.startKoin
import org.koin.core.inject
import org.koin.dsl.module
import org.slf4j.LoggerFactory

val koinModule = module {
    single { ApplicationInfoService(get(), get()) }
    single { ApplicationInfoProperties() }
    single { ServiceClient(get()) }
    single { Consul.builder().withUrl("http://localhost:8500").build() }
}

private val log = LoggerFactory.getLogger(HelidonServiceApplication::class.java)

object HelidonServiceApplication : KoinComponent {

    @JvmStatic
    fun main(args: Array<String>) {
        val startTime = System.currentTimeMillis()
        startKoin {
            modules(koinModule)
        }

        val applicationInfoService: ApplicationInfoService by inject()
        val consulClient: Consul by inject()
        val applicationInfoProperties: ApplicationInfoProperties by inject()
        val serviceName = applicationInfoProperties.name

        startServer(applicationInfoService, consulClient, serviceName, startTime)
    }
}

fun startServer(
    applicationInfoService: ApplicationInfoService,
    consulClient: Consul,
    serviceName: String,
    startTime: Long
): WebServer {
    val serverConfig = ServerConfiguration.create(Config.create().get("webserver"))

    val server: WebServer = WebServer
        .builder(createRouting(applicationInfoService))
        .addMediaSupport(JacksonSupport.create())
        .config(serverConfig)
        .build()

    server.start().thenAccept { ws ->
        val durationInMillis = System.currentTimeMillis() - startTime
        log.info("Startup completed in $durationInMillis ms. Service running at: http://localhost:" + ws.port())
        // register in Consul
        consulClient.agentClient().register(createConsulRegistration(serviceName, ws.port()))
    }

    return server
}

private fun createRouting(applicationInfoService: ApplicationInfoService) = Routing.builder()
    .get("/application-info", Handler { req, res ->
        val requestTo: String? = req.queryParams()
            .first("request-to")
            .orElse(null)

        res
            .status(Http.ResponseStatus.create(200))
            .send(applicationInfoService.get(requestTo))
    })
    .get("/application-info/logo", Handler { req, res ->
        res.headers().contentType(MediaType.create("image", "png"))
        res
            .status(Http.ResponseStatus.create(200))
            .send(applicationInfoService.getLogo())
    })
    .error(Exception::class.java) { req, res, ex ->
        log.error("Exception:", ex)
        res.status(Http.Status.INTERNAL_SERVER_ERROR_500).send()
    }
    .build()

private fun createConsulRegistration(serviceName: String, port: Int) = ImmutableRegistration.builder()
    .id("$serviceName-$port")
    .name(serviceName)
    .address("localhost")
    .port(port)
    .build()


<|start_filename|>quarkus-service/src/main/kotlin/io/heterogeneousmicroservices/quarkusservice/service/ExternalServiceClients.kt<|end_filename|>
package io.heterogeneousmicroservices.quarkusservice.service

import io.heterogeneousmicroservices.quarkusservice.model.ApplicationInfo
import org.eclipse.microprofile.rest.client.inject.RegisterRestClient
import javax.enterprise.context.ApplicationScoped
import javax.ws.rs.GET
import javax.ws.rs.Path
import javax.ws.rs.Produces

@ApplicationScoped
@Path("/")
interface ExternalServiceClient {
    @GET
    @Path("/application-info")
    @Produces("application/json")
    fun getApplicationInfo(): ApplicationInfo
}

@RegisterRestClient(baseUri = "http://helidon-service")
interface HelidonServiceClient : ExternalServiceClient

@RegisterRestClient(baseUri = "http://ktor-service")
interface KtorServiceClient : ExternalServiceClient

@RegisterRestClient(baseUri = "http://micronaut-service")
interface MicronautServiceClient : ExternalServiceClient

@RegisterRestClient(baseUri = "http://quarkus-service")
interface QuarkusServiceClient : ExternalServiceClient

@RegisterRestClient(baseUri = "http://spring-boot-service")
interface SpringBootServiceClient : ExternalServiceClient

<|start_filename|>ktor-service/src/test/kotlin/io/heterogeneousmicroservices/ktorservice/KtorServiceApplicationTest.kt<|end_filename|>
package io.heterogeneousmicroservices.ktorservice

import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
import com.fasterxml.jackson.module.kotlin.readValue
import com.orbitz.consul.Consul
import io.heterogeneousmicroservices.ktorservice.model.ApplicationInfo
import io.heterogeneousmicroservices.ktorservice.module.module
import io.ktor.application.Application
import io.ktor.http.ContentType
import io.ktor.http.HttpMethod
import io.ktor.server.testing.contentType
import io.ktor.server.testing.handleRequest
import io.ktor.server.testing.withTestApplication
import org.junit.jupiter.api.Assertions.assertArrayEquals
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.BeforeAll
import org.junit.jupiter.api.Test
import org.koin.core.context.startKoin
import org.koin.test.AutoCloseKoinTest
import org.koin.test.mock.declareMock
import java.io.File

class KtorServiceApplicationTest : AutoCloseKoinTest() {

    companion object {
        @BeforeAll
        @JvmStatic
        fun setUp() {
            startKoin {
                modules(koinModule)
            }
        }
    }

    // todo remove read `env` property for test purpose
    @Test
    fun testGet() {
        withTestApplication(Application::module) {
            declareMock<Consul>()
            val objectMapper = jacksonObjectMapper()
            handleRequest(HttpMethod.Get, "/application-info").apply {
                assertEquals(200, response.status()?.value)
                assertEquals(ContentType.Application.Json, response.contentType().withoutParameters())
                val expected = ApplicationInfo(
                    "ktor-service", ApplicationInfo.Framework("Ktor", 2018), null
                )
                val actual = objectMapper.readValue<ApplicationInfo>(
                    response.content ?: throw IllegalStateException("No content found in response")
                )
                assertEquals(expected, actual)
            }
        }
    }

    @Test
    fun testGetLogo() {
        withTestApplication(Application::module) {
            declareMock<Consul>()
            handleRequest(HttpMethod.Get, "/application-info/logo").apply {
                assertEquals(200, response.status()?.value)
                assertEquals(ContentType.Image.PNG, response.contentType())
                val expected = File("src/main/resources/logo.png").readBytes()
                assertArrayEquals(expected, response.byteContent)
            }
        }
    }
}

<|start_filename|>ktor-service/src/main/kotlin/io/heterogeneousmicroservices/ktorservice/config/ApplicationInfoProperties.kt<|end_filename|>
package io.heterogeneousmicroservices.ktorservice.config

import com.typesafe.config.ConfigFactory
import io.ktor.config.ApplicationConfig
import io.ktor.config.HoconApplicationConfig

class ApplicationInfoProperties(
    private val applicationInfoConfig: ApplicationConfig = HoconApplicationConfig(ConfigFactory.load().getConfig("application-info")),
    private val frameworkConfig: ApplicationConfig = applicationInfoConfig.config("framework"),
    val name: String = applicationInfoConfig.property("name").getString(),
    val frameworkName: String = frameworkConfig.property("name").getString(),
    val frameworkReleaseYear: Int = frameworkConfig.property("release-year").getString().toInt()
)

<|start_filename|>ktor-service/src/main/kotlin/io/heterogeneousmicroservices/ktorservice/service/ServiceClient.kt<|end_filename|>
package io.heterogeneousmicroservices.ktorservice.service

import com.orbitz.consul.Consul
import io.heterogeneousmicroservices.ktorservice.feature.ConsulFeature
import io.heterogeneousmicroservices.ktorservice.model.ApplicationInfo
import io.ktor.client.HttpClient
import io.ktor.client.engine.apache.Apache
import io.ktor.client.features.json.JacksonSerializer
import io.ktor.client.features.json.JsonFeature
import io.ktor.client.request.get
import kotlinx.coroutines.runBlocking

class ServiceClient(
    private val consulClient: Consul
) {

    private val httpClient = HttpClient(Apache) {
        install(JsonFeature) {
            serializer = JacksonSerializer()
        }
        install(ConsulFeature) {
            this.consulClient = this@ServiceClient.consulClient
        }
    }

    fun getApplicationInfo(serviceName: String): ApplicationInfo = runBlocking {
        httpClient.get<ApplicationInfo>("http://$serviceName/application-info")
    }
}

<|start_filename|>quarkus-service/src/main/kotlin/io/heterogeneousmicroservices/quarkusservice/web/ApplicationInfoResource.kt<|end_filename|>
package io.heterogeneousmicroservices.quarkusservice.web

import io.heterogeneousmicroservices.quarkusservice.service.ApplicationInfoService
import javax.inject.Inject
import javax.ws.rs.*
import javax.ws.rs.core.MediaType
import javax.ws.rs.core.Response

@Path("/application-info")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
class ApplicationInfoResource(
    @Inject private val applicationInfoService: ApplicationInfoService
) {

    @GET
    fun get(@QueryParam("request-to") requestTo: String?): Response =
        Response.ok(applicationInfoService.get(requestTo)).build()

    @GET
    @Path("/logo")
    @Produces("image/png")
    fun logo(): Response = Response.ok(applicationInfoService.getLogo()).build()
}

<|start_filename|>micronaut-service/build.gradle.kts<|end_filename|>
import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile

val micronautVersion: String by project
val logbackVersion: String by project
val junitVersion: String by project

plugins {
    application
    id("com.github.johnrengelman.shadow")
    kotlin("jvm")
    kotlin("kapt")
    jacoco
}

application {
    mainClassName = "io.heterogeneousmicroservices.micronautservice.MicronautServiceApplication"
}

dependencies {
    kapt("io.micronaut:micronaut-inject-java:$micronautVersion")
    implementation(enforcedPlatform("io.micronaut:micronaut-bom:$micronautVersion"))
    implementation(kotlin("stdlib"))
    implementation("io.micronaut:micronaut-runtime")
    implementation("io.micronaut:micronaut-http-client")
    implementation("io.micronaut:micronaut-http-server-netty")
    runtimeOnly("io.micronaut:micronaut-discovery-client")
    runtimeOnly("ch.qos.logback:logback-classic:$logbackVersion")
    runtimeOnly("com.fasterxml.jackson.module:jackson-module-kotlin")
    kaptTest("io.micronaut:micronaut-inject-java")
    testImplementation("io.micronaut.test:micronaut-test-junit5")
    testImplementation("org.junit.jupiter:junit-jupiter:$junitVersion")
}

tasks {
    withType<ShadowJar> {
        mergeServiceFiles()
    }
    withType<Test> {
        useJUnitPlatform()
    }
    withType<KotlinCompile> {
        kotlinOptions {
            jvmTarget = "11"
            freeCompilerArgs = listOf("-Xjsr305=strict")
        }
    }
}
