quarkusio / quarkus

Quarkus: Supersonic Subatomic Java.
https://quarkus.io
Apache License 2.0
13.77k stars 2.68k forks source link

Unable to extract REST API into shared gradle module #30748

Open andreas-eberle opened 1 year ago

andreas-eberle commented 1 year ago

Describe the bug

We have a Quarkus service that exposes a rest api and one that consumes the same rest api. Both are submodules of a multi-module gradle project and we use Kotlin.

Since we don't want to write the DTOs and the rest api interface twice, we added another gradle module where we extracted the rest client interface and the dtos to. Both Quarkus services now have this library module as a dependency. In the service that provides the API, I have now implemented this interface to provide the implementation for the api.

The library code looks like this (also checkout the reproducer below):

@Path("/books")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
interface BookApi {
    @GET
    fun getBooks(): List<BookDto>
}

data class BookDto(val name: String)

and the server code looks like this

class BookResource : BooksApi {
    override fun getBooks(): List<BookDto> {
        return listOf(BookDto("Abc"))
    }
}

Unfortunately, when you start the server, no resource is loaded and no endpoint is available.

We also tried adding @ApplicationScoped to the BookResource. Then we get warnings:

2023-01-30 18:01:41,134 WARN  [io.qua.arc.pro.BeanArchives] (build-39) Failed to index com.arconsis.books.BooksApi: Class does not exist in ClassLoader QuarkusClassLoader:Deployment Class Loader: DEV@68567e20
2023-01-30 18:01:43,021 WARN  [io.qua.arc.ComponentsProvider] (Quarkus Main Thread) Unable to load removed bean type [com.arconsis.planconfig.resource.BookResource]: java.lang.NoClassDefFoundError: com/arconsis/books/BooksApi

So it seems the interface is somehow not included in the code. Also the endpoint is not available.

When we add the @Path("/books") annotation to the BooksResource, we get an error like this:

2023-01-30 18:10:12,035 INFO  [io.qua.oid.dep.dev.OidcDevConsoleProcessor] (build-17) OIDC Dev Console: discovering the provider metadata at http://localhost:13501/realms/master/.well-known/openid-configuration
2023-01-30 18:10:12,240 WARN  [io.qua.arc.pro.BeanArchives] (build-31) Failed to index com.arconsis.books.BooksApi: Class does not exist in ClassLoader QuarkusClassLoader:Deployment Class Loader: DEV@17a7f733
2023-01-30 18:10:12,272 INFO  [io.qua.arc.pro.IndexClassLookupUtils] (build-31) Class for name: com.arconsis.books.BooksApi was not found in Jandex index. Please ensure the class is part of the index.
2023-01-30 18:10:14,061 ERROR [io.qua.run.boo.StartupActionImpl] (Quarkus Main Thread) Error running Quarkus: java.lang.reflect.InvocationTargetException
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)
    at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.base/java.lang.reflect.Method.invoke(Method.java:568)
    at io.quarkus.runner.bootstrap.StartupActionImpl$1.run(StartupActionImpl.java:104)
    at java.base/java.lang.Thread.run(Thread.java:833)
Caused by: java.lang.ExceptionInInitializerError
    at java.base/jdk.internal.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
    at java.base/jdk.internal.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:77)
    at java.base/jdk.internal.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
    at java.base/java.lang.reflect.Constructor.newInstanceWithCaller(Constructor.java:499)
    at java.base/java.lang.reflect.Constructor.newInstance(Constructor.java:480)
    at io.quarkus.runtime.Quarkus.run(Quarkus.java:70)
    at io.quarkus.runtime.Quarkus.run(Quarkus.java:44)
    at io.quarkus.runtime.Quarkus.run(Quarkus.java:124)
    at io.quarkus.runner.GeneratedMain.main(Unknown Source)
    ... 6 more
Caused by: java.lang.RuntimeException: Failed to start quarkus
    at io.quarkus.runner.ApplicationImpl.<clinit>(Unknown Source)
    ... 15 more
Caused by: java.lang.NoClassDefFoundError: com/arconsis/books/BooksApi
    at java.base/java.lang.ClassLoader.defineClass1(Native Method)
    at java.base/java.lang.ClassLoader.defineClass(ClassLoader.java:1012)
    at io.quarkus.bootstrap.classloading.QuarkusClassLoader.loadClass(QuarkusClassLoader.java:497)
    at io.quarkus.bootstrap.classloading.QuarkusClassLoader.loadClass(QuarkusClassLoader.java:457)
    at java.base/java.lang.Class.forName0(Native Method)
    at java.base/java.lang.Class.forName(Class.java:467)
    at com.arconsis.books.BookResource_Bean.<init>(Unknown Source)
    at io.quarkus.arc.setup.Default_ComponentsProvider.addBeans3(Unknown Source)
    at io.quarkus.arc.setup.Default_ComponentsProvider.getComponents(Unknown Source)
    at io.quarkus.arc.impl.ArcContainerImpl.<init>(ArcContainerImpl.java:117)
    at io.quarkus.arc.Arc.initialize(Arc.java:31)
    at io.quarkus.arc.runtime.ArcRecorder.initContainer(ArcRecorder.java:43)
    at io.quarkus.deployment.steps.ArcProcessor$generateResources844392269.deploy_0(Unknown Source)
    at io.quarkus.deployment.steps.ArcProcessor$generateResources844392269.deploy(Unknown Source)
    ... 16 more
Caused by: java.lang.ClassNotFoundException: com.arconsis.books.BooksApi
    at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:641)
    at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:188)
    at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:520)
    at io.quarkus.bootstrap.classloading.QuarkusClassLoader.loadClass(QuarkusClassLoader.java:507)
    at io.quarkus.bootstrap.classloading.QuarkusClassLoader.loadClass(QuarkusClassLoader.java:457)
    at io.quarkus.bootstrap.classloading.QuarkusClassLoader.loadClass(QuarkusClassLoader.java:507)
    at io.quarkus.bootstrap.classloading.QuarkusClassLoader.loadClass(QuarkusClassLoader.java:457)
    ... 30 more

2023-01-30 18:10:14,064 INFO  [io.qua.dep.dev.IsolatedDevModeMain] (main) Attempting to start live reload endpoint to recover from previous Quarkus startup failure

Furthermore, if we create a client like the following:

@RegisterRestClient(configKey = "books-api")
interface BooksClient : BooksApi

it is also not loaded when the application starts.

Expected behavior

We should be able to extract the DTOs and API definiton to a different module to reuse it in resources and clients in multiple projects in order to not duplicate the code.

Actual behavior

The interface of the API seems to get lost and the setup is not working.

How to Reproduce?

Reproducer: 2023-01-31-gradle-multimodule.zip

  1. Download & Unpack the reproducer
  2. Start the server by running ./gradlew :server:quarkusDev
  3. The should be an endpoint http://localhost:8080/books. But it is not there. You can test and see the behavior described above.
  4. Start the client by running ./gradlew :client:quarkusDev
  5. You can test and see the behavior described above.

Output of uname -a or ver

No response

Output of java -version

kotlin 1.7.22

GraalVM version (if different from Java)

No response

Quarkus version or git rev

Tested 2.16.0 and 2.15.3

Build tool (ie. output of mvnw --version or gradlew --version)

gradle

Additional information

This ticket is the result of this Zulip chat here: https://quarkusio.zulipchat.com/#narrow/stream/187030-users/topic/How.20to.20extract.20REST.20API.20into.20shared.20gradle.20module

quarkus-bot[bot] commented 1 year ago

You added a link to a Zulip discussion, please make sure the description of the issue is comprehensive and doesn't require accessing Zulip

This message is automatically generated by a bot.

quarkus-bot[bot] commented 1 year ago

/cc @evanchooly (kotlin), @geoand (kotlin), @glefloch, @quarkusio/devtools

aloubyansky commented 1 year ago

We are currently picking up tasks that are instances org.gradle.api.tasks.compile.AbstractCompile and org.jetbrains.kotlin.gradle.tasks.KotlinJvmCompile, while the compileKotlin task in this project is an instance of org.jetbrains.kotlin.gradle.tasks.KotlinCompile, which isn't on the Quarkus plugin's build classpath.

andreas-eberle commented 1 year ago

So, would it make sense for Quarkus to listen to KotlinCompile as well? I mean, I have the Kotlin JVM plugin in that library project. So no idea why "KotlinCompile" instead of "KotlinJvmCompile" is used.

aloubyansky commented 1 year ago

Yes, it would. We just need to figure out how to do that.

aloubyansky commented 1 year ago

The client project, otoh, is handled properly, btw.

andreas-eberle commented 1 year ago

Did you change anything in the client project? When I start the client project, I get the following error:

2023-02-01 10:14:56,591 INFO  [io.qua.dep.dev.IsolatedDevModeMain] (main) Attempting to start live reload endpoint to recover from previous Quarkus startup failure
2023-02-01 10:14:57,125 ERROR [io.qua.dep.dev.IsolatedDevModeMain] (main) Failed to start quarkus: java.lang.RuntimeException: io.quarkus.builder.BuildException: Build failure: Build failed due to errors
    [error]: Build step io.quarkus.arc.deployment.ArcProcessor#validate threw an exception: javax.enterprise.inject.spi.DeploymentException: javax.enterprise.inject.UnsatisfiedResolutionException: Unsatisfied dependency for type org.acme.BooksClient and qualifiers [@RestClient]
    - java member: org.acme.GreetingResource():booksClient
    - declared on CLASS bean [types=[org.acme.GreetingResource, java.lang.Object], qualifiers=[@Default, @Any], target=org.acme.GreetingResource]
    at io.quarkus.arc.processor.BeanDeployment.processErrors(BeanDeployment.java:1223)
    at io.quarkus.arc.processor.BeanDeployment.init(BeanDeployment.java:288)
    at io.quarkus.arc.processor.BeanProcessor.initialize(BeanProcessor.java:148)
    at io.quarkus.arc.deployment.ArcProcessor.validate(ArcProcessor.java:526)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)
    at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.base/java.lang.reflect.Method.invoke(Method.java:568)
    at io.quarkus.deployment.ExtensionLoader$3.execute(ExtensionLoader.java:909)
    at io.quarkus.builder.BuildContext.run(BuildContext.java:281)
    at org.jboss.threads.ContextHandler$1.runWith(ContextHandler.java:18)
    at org.jboss.threads.EnhancedQueueExecutor$Task.run(EnhancedQueueExecutor.java:2449)
    at org.jboss.threads.EnhancedQueueExecutor$ThreadBody.run(EnhancedQueueExecutor.java:1478)
    at java.base/java.lang.Thread.run(Thread.java:833)
    at org.jboss.threads.JBossThread.run(JBossThread.java:501)
Caused by: javax.enterprise.inject.UnsatisfiedResolutionException: Unsatisfied dependency for type org.acme.BooksClient and qualifiers [@RestClient]
    - java member: org.acme.GreetingResource():booksClient
    - declared on CLASS bean [types=[org.acme.GreetingResource, java.lang.Object], qualifiers=[@Default, @Any], target=org.acme.GreetingResource]
    at io.quarkus.arc.processor.Beans.resolveInjectionPoint(Beans.java:440)
    at io.quarkus.arc.processor.BeanInfo.init(BeanInfo.java:539)
    at io.quarkus.arc.processor.BeanDeployment.init(BeanDeployment.java:276)
    ... 13 more

    at io.quarkus.runner.bootstrap.AugmentActionImpl.runAugment(AugmentActionImpl.java:335)
    at io.quarkus.runner.bootstrap.AugmentActionImpl.createInitialRuntimeApplication(AugmentActionImpl.java:252)
    at io.quarkus.runner.bootstrap.AugmentActionImpl.createInitialRuntimeApplication(AugmentActionImpl.java:60)
    at io.quarkus.deployment.dev.IsolatedDevModeMain.firstStart(IsolatedDevModeMain.java:86)
    at io.quarkus.deployment.dev.IsolatedDevModeMain.accept(IsolatedDevModeMain.java:447)
    at io.quarkus.deployment.dev.IsolatedDevModeMain.accept(IsolatedDevModeMain.java:59)
    at io.quarkus.bootstrap.app.CuratedApplication.runInCl(CuratedApplication.java:149)
    at io.quarkus.bootstrap.app.CuratedApplication.runInAugmentClassLoader(CuratedApplication.java:104)
    at io.quarkus.deployment.dev.DevModeMain.start(DevModeMain.java:131)
    at io.quarkus.deployment.dev.DevModeMain.main(DevModeMain.java:62)
Caused by: io.quarkus.builder.BuildException: Build failure: Build failed due to errors
    [error]: Build step io.quarkus.arc.deployment.ArcProcessor#validate threw an exception: javax.enterprise.inject.spi.DeploymentException: javax.enterprise.inject.UnsatisfiedResolutionException: Unsatisfied dependency for type org.acme.BooksClient and qualifiers [@RestClient]
    - java member: org.acme.GreetingResource():booksClient
    - declared on CLASS bean [types=[org.acme.GreetingResource, java.lang.Object], qualifiers=[@Default, @Any], target=org.acme.GreetingResource]
    at io.quarkus.arc.processor.BeanDeployment.processErrors(BeanDeployment.java:1223)
    at io.quarkus.arc.processor.BeanDeployment.init(BeanDeployment.java:288)
    at io.quarkus.arc.processor.BeanProcessor.initialize(BeanProcessor.java:148)
    at io.quarkus.arc.deployment.ArcProcessor.validate(ArcProcessor.java:526)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)
    at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.base/java.lang.reflect.Method.invoke(Method.java:568)
    at io.quarkus.deployment.ExtensionLoader$3.execute(ExtensionLoader.java:909)
    at io.quarkus.builder.BuildContext.run(BuildContext.java:281)
    at org.jboss.threads.ContextHandler$1.runWith(ContextHandler.java:18)
    at org.jboss.threads.EnhancedQueueExecutor$Task.run(EnhancedQueueExecutor.java:2449)
    at org.jboss.threads.EnhancedQueueExecutor$ThreadBody.run(EnhancedQueueExecutor.java:1478)
    at java.base/java.lang.Thread.run(Thread.java:833)
    at org.jboss.threads.JBossThread.run(JBossThread.java:501)
Caused by: javax.enterprise.inject.UnsatisfiedResolutionException: Unsatisfied dependency for type org.acme.BooksClient and qualifiers [@RestClient]
    - java member: org.acme.GreetingResource():booksClient
    - declared on CLASS bean [types=[org.acme.GreetingResource, java.lang.Object], qualifiers=[@Default, @Any], target=org.acme.GreetingResource]
    at io.quarkus.arc.processor.Beans.resolveInjectionPoint(Beans.java:440)
    at io.quarkus.arc.processor.BeanInfo.init(BeanInfo.java:539)
    at io.quarkus.arc.processor.BeanDeployment.init(BeanDeployment.java:276)
    ... 13 more

    at io.quarkus.builder.Execution.run(Execution.java:123)
    at io.quarkus.builder.BuildExecutionBuilder.execute(BuildExecutionBuilder.java:79)
    at io.quarkus.deployment.QuarkusAugmentor.run(QuarkusAugmentor.java:160)
    at io.quarkus.runner.bootstrap.AugmentActionImpl.runAugment(AugmentActionImpl.java:331)
    ... 9 more
Caused by: javax.enterprise.inject.spi.DeploymentException: javax.enterprise.inject.UnsatisfiedResolutionException: Unsatisfied dependency for type org.acme.BooksClient and qualifiers [@RestClient]
    - java member: org.acme.GreetingResource():booksClient
    - declared on CLASS bean [types=[org.acme.GreetingResource, java.lang.Object], qualifiers=[@Default, @Any], target=org.acme.GreetingResource]
    at io.quarkus.arc.processor.BeanDeployment.processErrors(BeanDeployment.java:1223)
    at io.quarkus.arc.processor.BeanDeployment.init(BeanDeployment.java:288)
    at io.quarkus.arc.processor.BeanProcessor.initialize(BeanProcessor.java:148)
    at io.quarkus.arc.deployment.ArcProcessor.validate(ArcProcessor.java:526)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)
    at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.base/java.lang.reflect.Method.invoke(Method.java:568)
    at io.quarkus.deployment.ExtensionLoader$3.execute(ExtensionLoader.java:909)
    at io.quarkus.builder.BuildContext.run(BuildContext.java:281)
    at org.jboss.threads.ContextHandler$1.runWith(ContextHandler.java:18)
    at org.jboss.threads.EnhancedQueueExecutor$Task.run(EnhancedQueueExecutor.java:2449)
    at org.jboss.threads.EnhancedQueueExecutor$ThreadBody.run(EnhancedQueueExecutor.java:1478)
    at java.base/java.lang.Thread.run(Thread.java:833)
    at org.jboss.threads.JBossThread.run(JBossThread.java:501)
Caused by: javax.enterprise.inject.UnsatisfiedResolutionException: Unsatisfied dependency for type org.acme.BooksClient and qualifiers [@RestClient]
    - java member: org.acme.GreetingResource():booksClient
    - declared on CLASS bean [types=[org.acme.GreetingResource, java.lang.Object], qualifiers=[@Default, @Any], target=org.acme.GreetingResource]
    at io.quarkus.arc.processor.Beans.resolveInjectionPoint(Beans.java:440)
    at io.quarkus.arc.processor.BeanInfo.init(BeanInfo.java:539)
    at io.quarkus.arc.processor.BeanDeployment.init(BeanDeployment.java:276)
    ... 13 more
aloubyansky commented 1 year ago

Ah, I meant it properly locates the compileKotlin task in the client project, the dev mode still fails due to the issue in the library project.

andreas-eberle commented 1 year ago

@aloubyansky: Do you think this issue can be solved soon or should I tried to find some workarounds for the time being?

aloubyansky commented 1 year ago

@glefloch will you have some time in the coming days/weeks to look into this one? It doesn't look like I will.

qlereboursBS commented 1 year ago

I think we have the same issue on my project:

We have a multi module project setup with Quarkus and Gradle, with two modules at the moment:

application/build.gradle.kts:

plugins {
    kotlin("jvm") version "1.7.22"
    kotlin("plugin.allopen") version "1.7.22"
    id("io.quarkus")
    id("org.kordamp.gradle.jandex") version "0.11.0"
}

repositories {
    mavenCentral()
    mavenLocal()
}

val quarkusPlatformGroupId: String by project
val quarkusPlatformArtifactId: String by project
val quarkusPlatformVersion: String by project

dependencies {
    implementation(enforcedPlatform("${quarkusPlatformGroupId}:${quarkusPlatformArtifactId}:${quarkusPlatformVersion}"))
    implementation("io.quarkus:quarkus-resteasy-reactive")
    implementation("io.quarkus:quarkus-kotlin")
    implementation("io.quarkus:quarkus-vertx")
    implementation("io.quarkus:quarkus-resteasy-reactive-jackson")
    implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
    implementation("io.quarkus:quarkus-arc")
    testImplementation("io.quarkus:quarkus-junit5")
    testImplementation("io.rest-assured:rest-assured")

    implementation(project(":produit"))
}
// ... other things here

produit/build.gradle.kts:

plugins {
    kotlin("jvm") version "1.7.21"
    id("org.kordamp.gradle.jandex") version "0.11.0"
}
// ... other things here

Root's settings.gradle.kts:

rootProject.name = "testapp"

include("produit")
include("application")

Note that we tried to include the beans with Jandex (here's the application.properties):

quarkus.index-dependency.produit.group-id=our.package.name
quarkus.index-dependency.produit.artifact-id=produit

If we build the app with ./gradlew build (fast-jar or even with uber-jar), and run it with java -jar application/build/quarkus-app/quarkus-run.jar, the endpoints we defined in the produit module are reachable (therefore detected by the Quarkus app). However, when running in dev with ./gradlew quarkusDev they are not.

@andreas-eberle could you confirm that it's the same issue? Did you find any workaround? Thanks

glefloch commented 1 year ago

I will do my best and try to look into it quickly.

andreas-eberle commented 1 year ago

@qlereboursBS: You are right! When I first build it with ./gradlew build, it works like you would expect. So it seems this is only broken for quarkusDev.

andreas-eberle commented 1 year ago

Hey @glefloch ,

did you make any progress? Is there something how we could support you? However, I have not really worked with the internals of Quarkus yet :(

Having this would be really important to us to allow us to reuse code more easily. Thanks a lot for your efforts!

glefloch commented 1 year ago

Not really any progress on my side. I definitely miss time for now. I will do my best!

tokazio commented 1 year ago

Hello, same problem here with a gradle composite build

I get

2023-09-25 17:30:05,235 WARN  [io.qua.arc.pro.BeanArchives] (build-48) Failed to index fr.tokazio.gedeon.core.Descriptor: Class does not exist in ClassLoader QuarkusClassLoader:Deployment Class Loader: DEV@3943a2be
2023-09-25 17:30:05,237 WARN  [io.qua.dep.ste.ReflectiveHierarchyStep] (build-48) Unable to properly register the hierarchy of the following classes for reflection as they are not in the Jandex index:
    - fr.tokazio.gedeon.core.Descriptor (source: ResteasyServerCommonProcessor > fr.tokazio.gedeon.quarkus.QuarkusController[fr.tokazio.gedeon.core.Descriptor up(org.jboss.resteasy.plugins.providers.multipart.MultipartFormDataInput input)])
Consider adding them to the index either by creating a Jandex index for your dependency via the Maven plugin, an empty META-INF/beans.xml or quarkus.index-dependency properties.

...

Caused by: java.lang.ClassNotFoundException: fr.tokazio.gedeon.core.Descriptor
    at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:641)
    at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:188)
    at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:521)
    at io.quarkus.bootstrap.classloading.QuarkusClassLoader.loadClass(QuarkusClassLoader.java:516)
andreas-eberle commented 7 months ago

This might be related to #35577 and it seems it works now. @tokazio : Can you check if it also works for you?

nuwak commented 6 months ago

I seem to have the same problem. There is no guide on how to set up a multi-module kotlin, gradle project with Quarkus? I just started studying this framework, I really like it, but this is a critical problem that does not allow me to completely switch to it.

okarmazin commented 2 months ago

https://github.com/quarkusio/quarkus/issues/35577#issuecomment-2137027868

FWIW workaround was found for Kotlin Multiplatform (link), at least for purely common Kotlin. You can create a shared-quarkushack sibling JVM module and symlink its src/main/kotlin to the shared module's src/commonMain/kotlin.

Then you can consume shared-quarkushack instead of shared from your Quarkus application.

As of Quarkus 3.13.3 I use this workaround to share API DTOs between a Quarkus backend and a Kobweb-based frontend application. The Kobweb app consumes shared directly.