Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Multiple JAX-RS Applications #11415

Closed
38leinaD opened this issue Aug 17, 2020 · 23 comments
Closed

Multiple JAX-RS Applications #11415

38leinaD opened this issue Aug 17, 2020 · 23 comments
Labels
area/resteasy kind/enhancement New feature or request triage/wontfix This will not be worked on

Comments

@38leinaD
Copy link
Contributor

Description
When developing bigger microservices or monolithic applications, it is useful to be able to define multiple JAX-RS applications.
Currently, Quarkus only allows to have a single Application: See https://github.com/quarkusio/quarkus/blob/master/extensions/resteasy-server-common/deployment/src/main/java/io/quarkus/resteasy/server/common/deployment/ResteasyServerCommonProcessor.java#L193

Obviously, this involves more ceremony by the developer who makes use of the feature (implementing a JaxRS Application class and implementing the getClasses() method to assign the different resources), but as this is a "power-user" feature, it will not impact the standard user.

@38leinaD 38leinaD added the kind/enhancement New feature or request label Aug 17, 2020
@famod
Copy link
Member

famod commented Aug 17, 2020

FWIW, back in march I started a discussion on Zulip regarding "Multiple/modular Swagger UIs": https://quarkusio.zulipchat.com/#narrow/stream/187030-users/topic/Multiple.2Fmodular.20Swagger.20UIs

@kenfinnigan
Copy link
Member

What's the advantage to multiple JAX-RS Applications vs different Resource @paths?

@famod
Copy link
Member

famod commented Aug 21, 2020

The ability to have different ExceptionMappers, for instance.

@kenfinnigan
Copy link
Member

@famod can you elaborate on the use case for that?

I don't understand why different exception mappers for the same problem are needed?

@famod
Copy link
Member

famod commented Aug 21, 2020

Well, you might want to have a very modular structure in a larger Quarkus app. In this case you'll likely want to separate the exception classes from each other that are specific for certain "subdomains".
So this would not be about handling the same problem in different ways (although in some special edge cases that could come in handy as well).

@kenfinnigan
Copy link
Member

Ok, I think I see what you mean.

However, I don't think this feature is something that would be easy to implement as it would mean having to segregate CDI beans between the different JAX-RS Applications. And right now I don't think adding classes to getClasses() does anything for CDI. Even classes not returned from it would become CDI Beans, I think anyway.

I could be wrong on the above, but that's my understanding

@38leinaD
Copy link
Contributor Author

Adding to @famod's comment, it is not just about ExceptionMappers but all JAX-RS constructs that can be attached on the application-level: Exception mappers, Message Body Reader/Writer to support additional type mappings, ContainerResponseFilters, etc.
All these constructs would benefit from a level of isolation if the Quarkus application contains multiple independent subdomains.

@kenfinnigan
Copy link
Member

I see what you mean, but I'm saying I'm not sure such isolation ability is something easily achievable in Quarkus, or even something that should be done

@38leinaD
Copy link
Contributor Author

38leinaD commented Aug 31, 2020

However, I don't think this feature is something that would be easy to implement as it would mean having to segregate CDI beans between the different JAX-RS Applications. And right now I don't think adding classes to getClasses() does anything for CDI. Even classes not returned from it would become CDI Beans, I think anyway.

I am not sure I understand what makes it "not easy" to implement. Today, there is only a single JaxRS application; so, there is no need to map rest resources to applications. This information/mapping would need to be collected during build-time and be used to instanciate/configure multiple servlets (one for each applicaton).
The getClasses() method (assuming it just returns a static list and does not do anything crazy), could be called during build-time, to see which resources belong to which application and us this information in the previous step i outlined.

Maybe this last part is not as easy as i think; and it also sounds like it can cause problems; but then there could be a Quarkus specific API to convey the same infromation in a different way. E.g. offer a builditem in the quarkus build for applications to produce in their own extensions; or a config in the application.properties.

I am starting to understand what you mean with segregating CDI beans; could you eleborate on how it is done today in the resteasy extension? I.e. how are the resource beans, or the other providers selected during runtime and tied to the JaxRS application. I understand, that there is no way in CDI to segregate beans, but already today, resteasy/jaxrs in quarkus needs to tie the CDI bean of the resource to the resteasy/jaxrs application in some way...

@38leinaD
Copy link
Contributor Author

38leinaD commented Sep 3, 2020

Ok, i have been giving this some more thought and investigated as well.

Why is this useful?

I see this as an important feature to support the migration of bigger microservices or monoliths that are to be broken up to quarkus.
From own experience, I can tell you that is what as rather smooth ride up to now to migrate a big JEE application to quarkus. The only item that is left and I see problematic, is that there is no way at all to support multiple JAX-RS applications.
To me, many serious applications that are not "microserviced" down to inifinity from the start might have some kind of client-facing rest-services and another one facing other internal services (e.g. pulling reports, accessing managing masterdata).
These APIs might be different in form and shape how data is structured/transported/secured. I.e. different Message Body Reader/Writer, ExceptionMappers, ResponseFilters, ...
When migrating to Quarkus there is now the option to immediately, either seperate these out into different mircroservices or mush it all together into a single JaxRS application. I personally dont like both options for the first step and would prefer a smoother migration.

I would be happy with a hidden feature to support multiple JaxRS endpoints that does not have to be 100% JAX-RS spec compliant but gives the option. Similar to how Custom CDI scopes are supported by Arc; just not via CDI extensions that are enirely runtime-based but some possibility to do it in a custom Quarkus Extension. I.e. Application.getClasses() might not be a good fit for Quarkus, so instead offer a custom way via the build for anyone to tie resources to applications. E.g. new JaxRsApplicationResourceMappingBuildItem(applicationClass, Set.of(resourceAClass, resourceBClass)). Obviously, this additional ceremony would only be needed in case there are multiple JaxRS applciation discovered. If there only is one, all behaves as today.

One important point to note is that each Jax-RS application has a one-to-one mapping to its own servlet. So, we would have as many servlets as Jax-RS applications. I don't see anything wrong with that level of isolation as already today there are a lot of different servlets.

What is needed for it?

I have looked around how it looks today; basically, these are the main classes that are involved:

extensions/resteasy/deployment/src/main/java/io/quarkus/resteasy/deployment/ResteasyServletProcessor.java
Consume: ResteasyServerConfigBuildItem

extensions/resteasy/deployment/src/main/java/io/quarkus/resteasy/deployment/ResteasyStandaloneBuildStep.java
Consume: ResteasyDeploymentBuildItem
Produce/Consumes: ResteasyStandaloneBuildItem

extensions/resteasy-server-common/deployment/src/main/java/io/quarkus/resteasy/server/common/deployment/ResteasyServerCommonProcessor.java
Produce: ResteasyDeploymentBuildItem,ResteasyServerConfigBuildItem

extensions/resteasy/deployment/src/main/java/io/quarkus/resteasy/deployment/ResteasyBuiltinsProcessor.java
Consume: ResteasyDeploymentBuildItem

From a technical perspective, it looks like all *BuildItems need to be changed to MultiBuildItems and there needs to be be some way to correlate them; so, we know which ResteasyServerConfigBuildItem belongs to which ResteasyDeploymentBuildItem. To me, this is the @ApplicationPath value of the application.

@kenfinnigan : Regarding your comment on segregating CDI beans: Again, i am not 100% sure I understood the comment and if there is some problem lurking that I don't see currently. But maybe you mean what i describe with JaxRsApplicationResourceMappingBuildItem?

Also, i see getClasses() beeing called today during runtime/startup from resteasy. So, there would be the option to synthesize this method based on the data collected via JaxRsApplicationResourceMappingBuildItem. I have not looked into resteasy, but i assume it will use the results of getClasses to ask CDI for the bean which i assume should just work....!?

I would be heavily interested in seeing this feature happen and also willing to contribute. Let me know your thoughts. If there is zero change to see this happen, it would be good for me to know early because this might be a become a showstopper in our Quarkus migration journey.

@kenfinnigan
Copy link
Member

@38leinaD it's not just the JAX-RS side of things that needs amending, as far as I understand. It would also be necessary to segregate the CDI container (ArC) so that beans for one JAX-RS application are not injectable into the other.

My understanding is that the getClasses() on the Application ensures that only those particular classes are available to that JAX-RS Application, which means that separation needs to filter down into the CDI container to ensure beans don't cross between JAX-RS Applications.

Is that right?

@FroMage
Copy link
Member

FroMage commented Sep 3, 2020

Leaving aside the build-time complexities of supporting this in Quarkus, we could ask the RESTEasy team if multiple applications are even supported by RESTEasy at runtime in the same module/classloader. Given what I know about statics and ThreadLocals in the runtime I just don't know if it's even meant to be supported. Probably @asoldano will know?

As to whether this would be good for Quarkus, originally I would have said no since the workaround is simple: make those separate Quarkus applications. But now that we try to support bigger applications, it's not clear if we should reject it out of hand, or if we should first consider the effort/risk/benefit ratio to decide this. It sounds to me like quite some work to support (if the runtime supports it at all) and maintain for a fairly edge case IMO.

@38leinaD
Copy link
Contributor Author

38leinaD commented Sep 3, 2020

@38leinaD it's not just the JAX-RS side of things that needs amending, as far as I understand. It would also be necessary to segregate the CDI container (ArC) so that beans for one JAX-RS application are not injectable into the other.

My understanding is that the getClasses() on the Application ensures that only those particular classes are available to that JAX-RS Application, which means that separation needs to filter down into the CDI container to ensure beans don't cross between JAX-RS Applications.

Is that right?

Maybe getClasses in the JAX-RS spec has a broader scope, but from my experience with writing rest services, it usually boils down to returning resource-classes, providers and exeception mappers from the getClasses method. If we are talking about "segregate the CDI container (ArC) so that beans for one JAX-RS application are not injectable into the other", i am not sure what that means realistically; can a sub-resource be injected in another resource? With exception mappers and providers, there is no injection involved i think.

Is it possible in the getClasses to just define random bean-classes that are injecteable via @Inject? If it is possible with Jax-Rs indeed, i have never used it. And you can always do an @Inject for any CDI beans from within your resources. There is no segregation that only CDI beans that are listed in the getClasses can be injected.
Maybe the questions is also: where is the sweet stop of what should be supported?

@sberyozkin
Copy link
Member

@38leinaD, @famod Hi,

Adding to @famod's comment, it is not just about ExceptionMappers but all JAX-RS constructs that can be attached on the application-level: Exception mappers, Message Body Reader/Writer to support additional type mappings, ContainerResponseFilters, etc. All these constructs would benefit from a level of isolation if the Quarkus application contains multiple independent subdomains.

This is what DynamicFeature is for where one can attach the specific providers to the specific resource methods (i.e, specific paths). Does it work ?

@asoldano
Copy link
Contributor

asoldano commented Sep 3, 2020

Leaving aside the build-time complexities of supporting this in Quarkus, we could ask the RESTEasy team if multiple applications are even supported by RESTEasy at runtime in the same module/classloader. Given what I know about statics and ThreadLocals in the runtime I just don't know if it's even meant to be supported. Probably @asoldano will know?

AFAIR this is not supported currently; the idea is that a JavaEE / JakartaEE deployment (associated to its classloader) can possibly be a JAX-RS deployment. Each deployment has a single Application.

@38leinaD
Copy link
Contributor Author

38leinaD commented Sep 3, 2020

@asoldano

Leaving aside the build-time complexities of supporting this in Quarkus, we could ask the RESTEasy team if multiple applications are even supported by RESTEasy at runtime in the same module/classloader. Given what I know about statics and ThreadLocals in the runtime I just don't know if it's even meant to be supported. Probably @asoldano will know?

AFAIR this is not supported currently; the idea is that a JavaEE / JakartaEE deployment (associated to its classloader) can possibly be a JAX-RS deployment. Each deployment has a single Application.

I understand that one resteasy deployment has a single application. But why is it not possible to have multiple resteasy deployments on the same classloader? I.e. there is no limitation in JavaEE to define multiple Jax-RS applications in a single war/classloader. My assumption is that if Quarkus uses resteasy, which is also used in appservers (?), resteasy in quarkus should also not have a problem to have multiple resteasy deployments. Each deployment is tied to its own servlet.
JBoss is also using resteasy, right? I am not sure I understand yet why there would be as difference in what is theoretically possible with resteasy on Jboss versus Quarkus.

@asoldano
Copy link
Contributor

asoldano commented Sep 3, 2020

uh, I just realized there was already a discussion about supporting this in WildFly and it looks like it's only partially working. You might want to read the long discussion that started with this message https://www.eclipse.org/lists/jaxrs-dev/msg00564.html where the spec lead was saying that the spec is not really stating this is supported or not and some clarifications would be needed.

@asoldano
Copy link
Contributor

asoldano commented Sep 3, 2020

@ronsigal, I'm sure you have better memories of the discussion and can help here.

@38leinaD
Copy link
Contributor Author

38leinaD commented Sep 3, 2020

Just a quick summary/opinion based on all the good comments:
I understand now that there are problems regarding segregation of CDI beans, but I am not sure if this is really something to strife for. Regarding the use case in https://issues.redhat.com/browse/RESTEASY-1709, the person could have just used @Inject MetricsApplication application. Yes, not perfect because the provider is not reusable, but it is an option.
Quarkus is not Jax-RS spec compliant anyway;(getClasses() etc. is not supported); so why not support multiple JaxRS applications but without the segregation? That's my opinion :-)

Also as i understood it, there really is no limitation in resteasy to have multiple jaxrs deployments in the same war/classloader. it works in jboss/wildfly (just tried it out on jboss eap 7.1); so it should also not be a problem in quarkus!? Is that a correct assumption?

@famod
Copy link
Member

famod commented Sep 3, 2020

@sberyozkin

This is what DynamicFeature is for where one can attach the specific providers to the specific resource methods (i.e, specific paths). Does it work ?

You mean something like this? https://access.redhat.com/documentation/en-us/red_hat_fuse/7.3/html/apache_cxf_development_guide/jaxrs20filters#JAXRS20Filters-DynamicBinding

Looks promising. Worth a look I guess. Is there anything special to be aware of in Quarkus when using this approach?

@FroMage
Copy link
Member

FroMage commented Sep 4, 2020

Also as i understood it, there really is no limitation in resteasy to have multiple jaxrs deployments in the same war/classloader. it works in jboss/wildfly (just tried it out on jboss eap 7.1); so it should also not be a problem in quarkus!? Is that a correct assumption?

Not necessarily trivial, because this only works in the servlet mode, if it works, which means in Quarkus you'll need to be in servlet mode which is slower than the default mode.

And from what @asoldano says it's not clear if RESTEasy even supports having multiple deployments in the same class loader. Frankly by looking at the code I'm really unsure but I think it doesn't.

@38leinaD
Copy link
Contributor Author

38leinaD commented Sep 4, 2020

Ok, I was not aware that there are different modes in resteasy for running inside a servlet container and not. But I would have expected quarkus to use that servlet container mode as it has a servlet container.
So, seems like it really is not as easy to support multiple applications... :-/

@sberyozkin the dynamic feature looks indeed interesting. @famod let me know if you have tried it out and what your result is. I just tried it and it does not seem to work. The dynamic feature is called for each resource method, but when i configure a provider (i tried with an exceptionmapper) it seemed to have absolutely no impact...

@geoand
Copy link
Contributor

geoand commented Jul 27, 2021

Although not specifically targeting this issue, one can now do what is mentioned here by utilizing https://quarkus.io/version/main/guides/all-config#quarkus-core_quarkus.class-loading.removed-resources-removed-resources.

For example, if you have an Application class in the com.example:shared Maven artifact, you can exclude it
by adding

quarkus.class-loading.removed-resources."com.example\:shared"=com/example/MyApplication.class

The capability was introduced in #18121

@geoand geoand closed this as completed Jul 27, 2021
@geoand geoand added the triage/wontfix This will not be worked on label Jul 27, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area/resteasy kind/enhancement New feature or request triage/wontfix This will not be worked on
Projects
None yet
Development

No branches or pull requests

8 participants