3
votes

In Spring it's possible to define bean dependencies in separate modules, which are then resolved via the classpath at runtime. Is it possible to do something similar in Quarkus?

For example, a multi-module setup that looks like this:

- service
- service-test
- service-artifact

In Spring it's possible to define @Configuration in the service module, that resolves concrete dependencies at runtime via the classpath of its current context, either service-test or service-artifact, allowing injection of dummy or test dependencies when under test, and real ones in the production artifact.

For example, a class in service requires an instance of SomeInterface. The implementation of SomeInterface is defined in either the -test or -artifact module. The service module has no direct dependency on either the -test or -artifact modules.

Some code:

In the service module:

@ApplicationScoped
class OrderService(private val repository: OrderRepository) {
    fun process(order: Order) {
        repository.save(order)
    }
}

interface OrderRepository {
    fun save(order: Order)
}

In the service-test module:

class InMemoryOrderRepository : OrderRepository {
    val orders = mutableListOf<Order>()
    override fun save(order: Order) {
        orders.add(order)
    }
}

class OrderServiceTestConfig {
    @ApplicationScoped
    fun orderRepository(): OrderRepository {
        return InMemoryOrderRepository()
    }
}


@QuarkusTest
class OrderServiceTest {

    @Inject
    private lateinit var service: OrderService

    @Test
    fun `injected order service with resolved repository dependency`() {
        // This builds and runs OK            
        service.process(Order("some_test_order"))
    }
}

Where I have tried to replicate a Spring-style setup as above in Quarkus, ArC validation is failing with UnsatisfiedResolutionException on the build of the service module, even though everywhere it is actually consumed provides the correct dependencies; a test successfully resolves the dependency and passes.

How do I achieve the separation of dependency interface from the implementation, and keep ArC validation happy, with Quarkus?

(Note: this behaviour occurs with Java and Maven also.)

I have included a maven example here. Note that ./mvnw install fails with the UnsatisfiedResolutionException but that it's possible to build and run the test successfully using ./mvnw test.

Build files:

root project build.gradle.kts:

import org.jetbrains.kotlin.gradle.tasks.KotlinCompile

    plugins {
        kotlin("jvm") version "1.3.72"
        kotlin("plugin.allopen") version "1.3.72"
    }

allprojects {

    group = "my-group"
    version = "1.0.0-SNAPSHOT"

    repositories {
        mavenLocal()
        mavenCentral()
    }
}

subprojects {

    apply {
        plugin("kotlin")
        plugin("kotlin-allopen")
    }

    java {
        sourceCompatibility = JavaVersion.VERSION_11
        targetCompatibility = JavaVersion.VERSION_11
    }
    
    allOpen {
        annotation("javax.ws.rs.Path")
        annotation("javax.enterprise.context.ApplicationScoped")
        annotation("io.quarkus.test.junit.QuarkusTest")
    }

    apply {
        plugin("kotlin")
    }

    dependencies {
        implementation("org.jetbrains.kotlin:kotlin-reflect")
        implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
    }

    tasks.withType<KotlinCompile> {
        kotlinOptions.jvmTarget = JavaVersion.VERSION_11.toString()
        kotlinOptions.javaParameters = true
    }
}

build.gradle.kts for service:

import io.quarkus.gradle.tasks.QuarkusDev

plugins {
    id("io.quarkus") version "1.9.1.Final"
}

apply {
    plugin("io.quarkus")
}

dependencies {
    implementation(project(":common:model"))
    implementation(enforcedPlatform("io.quarkus:quarkus-universe-bom:1.9.1.Final"))
    implementation("io.quarkus:quarkus-kotlin")
}

build.gradle.kts for service-test:

import io.quarkus.gradle.tasks.QuarkusDev

plugins {
    id("io.quarkus") version "1.9.1.Final"
}

apply {
    plugin("io.quarkus")
}

dependencies {
    implementation(project(":service"))
    implementation(enforcedPlatform("io.quarkus:quarkus-universe-bom:1.9.1.Final"))
    implementation("io.quarkus:quarkus-kotlin")
    testImplementation("io.quarkus:quarkus-junit5")
}
1
What are you using for creating a multi-module project? Maven or Gradle? Could you share relevant parts of your build files as an example?Oresztesz
I'm using Gradle (and Kotlin).. yes let me add themjimmy_terra
This is what I found so far for Maven in the documentation for the multi-module builds: quarkus.io/guides/maven-tooling#multi-module-maven In Gradle it's more difficult: quarkus.io/guides/gradle-tooling#multi-module-gradle. Have you tried what's written here?Oresztesz
Thanks yes, I am using the META-INF/beans.xml suggestion in each project. No joy.jimmy_terra
OK, thanks for all the additional info. I will take a look on it.Oresztesz

1 Answers

3
votes

Try to use instance injection (java example):

import javax.enterprise.inject.Instance;
...
@Inject
Instance<MyBeanClass> bean;
...
bean.get(); // for a single bean
bean.stream(); // for a collection