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

[Architecture Board] Clean up SPI to provide implementations #43

Closed
OndroMih opened this issue Jul 12, 2018 · 21 comments
Closed

[Architecture Board] Clean up SPI to provide implementations #43

OndroMih opened this issue Jul 12, 2018 · 21 comments
Labels
Architecture board Issues across more MP specifications

Comments

@OndroMih
Copy link
Contributor

Many MP specs provide an SPI for implementations to provide implementations of some API interfaces. Usually, the following pattern is used:

  • CoreInterface interface in the root API package
  • a CoreInterfaceResolver abstract class in an SPI package
  • a static resolver method which provides a default CoreInterface instance
  • the resolver method uses the service loader mechanism to load a CoreInterfaceResolver implementation which provides an instance of CoreInterface
  • alternatively, CoreInterfaceResolver implementation can also be set globally with a static setInstance method (to simplify providing an implementation in an OSGi environment)

Several issues have been raised to remove the setInstance method from the API artifact, claiming that separating it into an SPI package within the same artifact isn't enough to prevent API consumers using it: eclipse/microprofile-rest-client#103, eclipse/microprofile-open-api#224, eclipse/microprofile-config#364

@OndroMih OndroMih added the Architecture board Issues across more MP specifications label Jul 12, 2018
@OndroMih
Copy link
Contributor Author

I suggest a solution to consider for all specs:

  • create a separate shared SPI artifact that provides a mechanism (a static method) to load any SPI class (by class name)
  • this shared SPI artifact is only a stub and implementations provide their own version.
  • the SPI artifact is used as a provided dependency for all MP API artifacts that need it. It would be just to compile the API artifacts and it wouldn't become a transitive dependency. A real SPI artifact would be provided by the container/implementation

This would have the following advantages:

  • the implementation SPI would be removed from API artifacts
  • the SPI artifact would be invisible to API consumers (it wouldn't become a transitive dependency)
  • MP implementations would be free to replace the stub with a completely custom way to load implementations
  • the shared MP SPI artifact would be very lightweight, because it would be only a stub
  • the SPI mechanism wouldn't be duplicated in all MP specs that need it

@OndroMih
Copy link
Contributor Author

However, @rmannibucau and I also suggest that this Java SE based SPI mechanism should only be used in MP specs that can't rely on CDI. All other specs should only support CDI-based SPI mechanism and expect a CDI bean that implements a specific interface.

@OndroMih
Copy link
Contributor Author

OndroMih commented Jul 12, 2018

Using CDI should be as easy as CDI.current().select(CoreInterfaceResolver.class).get() instead of calling a static resolver method.

@struberg
Copy link

basically agree. But be careful to not put every SPI class in the same pot.
There are actually 2 different use cases:

  1. SPI which is intended to pick up the provider implementation
  2. SPI which are intended to be implemented by users with the goal to enhance the core mechanism in a portable way. Those interfaces definitely belongs to the api jar!

@rmannibucau
Copy link

rmannibucau commented Jul 12, 2018

You cant use CDI.select, this is insanely slow and at the end static utilities are worde than normal injections so no real need and respecting cdi programming model solves it smoothly and enhance the platform consistency. Only goods and no real need to spli api/spi in 2 jars anymore ;).

edit: select was missing turning my sentence in sthg weird

@struberg
Copy link

I assume s/cant/can/?
true, only mp-config really needs the provider. All the other specs can rely on CDI (or Spring, whatever) to get providers injected.

@rmannibucau
Copy link

@struberg "select" got eaten by the keyboard, the static lazy lookup i meant

@jroper
Copy link
Member

jroper commented Jul 19, 2018

To expand on @struberg's second use case for SPIs, here's a concrete instance of that in MicroProfile Reactive Streams Operators. The API artifact provides a set of builders, these builders are concrete classes, they don't provide any real business logic, they just build graphs of streams (a graph being a set of stages, eg, map, filter, etc). These builders build reactive streams publishers/subscribers/etc, and they do that by creating a graph, and then passing that graph to the vendor implementation through the SPI, this is where the actual work is then done by an implementation to turn that graph that describes the stream into a stream that can be run.

In this case, the SPI is a combination of two things, a simple interface that the implementations implement, plus the concrete classes that represent the graph built by the builder API. While the interface itself could be extracted into a separate artifact, the API depends on the concrete classes, and so even if they were extracted, the api artifact would have to depend on the SPI artifact and so it wouldn't make sense to extract it.

One of the reasons that it's done this way is as @struberg says, to allow customisation. There are some very big differences in the way reactive streams can be implemented - particularly when it comes to execution contexts, such as how context gets propagated, as well as monitoring and tracing. We want end users to be able to plug in different implementations for different use cases. MicroProfile containers will provide a default implementation, but users can, on a case by case basis, use different implementations as they please. Sometimes also it may be necessary to customise the default implementation for a particular use case within an application, so this also allows for that. So while we don't necessarily want this SPI exposed to end users, we do want it exposed to the libraries they use. Of course Java/Java EE offers no way to differentiate between library code and end user code.

@rmannibucau
Copy link

If there is a mp reactive one day, it must be cdi integrated and all based on interfaces and implemented as cdi beans IMHO to 1. avoid the cost of the provider (if you say you cache it then it doesnt work in most cases in java) and 2. be integrated to the platform.

SetInstance is never useful since cdi is here for all impl. Only exception could be config which doesnt need it thanks a clean design "à la old EE".

So plezse just stick to the platform and use cdi.

@jroper
Copy link
Member

jroper commented Jul 19, 2018

That's like saying we should only use CDI managed instances of java.util.List. I suggest you take a closer look at what MicroProfile Reactive Streams Operators actually is before making broad claims of what it should or shouldn't do. It's essentially JDK8 java.util.streams, but for asynchronous streams, and it's possible that after incubation in MicroProfile, that this spec will be adopted by the JDK itself. Two days ago the spec lead for CDI joined the MP Reactive hangout, and he seems happy that we are on the right track with how a project like that should integrate with CDI.

@rmannibucau
Copy link

@jroper No, you got it wrong, this is like saying you can always have the provider available through an injection and compose it with java.util instances. Current reactive spec is not integrated/integrable to a CDI based app (so a MP app), it is just a plain standalone spec which is not consistent with the platform and doesn't bring anything to the platform itself (this work must be done IMHO but is more important than the spec taken alone, it must replace the fault-tolerance spec and integrate smoothly with CompletionStage already built-in in JAX-RS). So at the end the API should be platform driven and not isoalted and then we check how we can integrate.

@rmannibucau
Copy link

rmannibucau commented Jul 19, 2018

Which is from config, the only exception of all spec.

@struberg
Copy link

No @rmannibucau the link @starksm64 posted is the setInstance method. Afaiu this merhod is not needed anymore, nowhere. Not even in mp-config.
It is purely for ancient OSGi containers and not needed anymore with more modern OSGi service discovery. We should clarify this with a few OSGi experts and have them provide feedback.

The point you likely mean is that mp-config is probably a rare example which cannot be CDI-first as it is needed during container startup already. This is actually a separated topic and we should split those 2 up in 2 tickets.

@rmannibucau
Copy link

Yep, misunderstood the link. Old osgi containers dont need it too (geronimo has 20 years and never need it) so i guess it is an ibm open liberty leak?

@jroper
Copy link
Member

jroper commented Jul 20, 2018

@rmannibucau You're actually spot on when you say:

and doesn't bring anything to the platform itself

This is 100% true, MicroProfile Reactive Streams Operators doesn't bring anything to the platform itself. In isolation, it's not useful. However, it's necessary for other specs, for example MicroProfile Reactive Messaging, which entirely dependent on CDI. It's MicroProfile Reactive Messaging that provides the engine to the messaging builders that users return, and this design is necessary because different streams need engines configured in different ways, for example, providing access to different CDI scopes, depending on where they are used. If MicroProfile Reactive Streams Operators required an engine to be provided by the CDI container, then you would be stuck with using one engine, and all your streams would only be bound to one particular CDI context, and it wouldn't allow, for example, the messaging spec to ensure that streams ran in a messaging CDI context while a WebSocket spec ensured streams ran in the request context. There's been a lot of discussion about exactly where MicroProfile Reactive Streams Operators fits in the context of MicroProfile, including the very things you've been mentioning. It integrates well into CDI, but not by itself, it integrates well as used by the other specs.

@rmannibucau
Copy link

@jroper hmm, lead me to 2 comments:

  1. if it doesnt bring anything to the platform maybe not do it now? CompletionStage is a correct way to bridge it, just needs a CDI bean enabling more composition and error handling (fault tolerance). Any other top layers sounds overkill and risky regarding the JDK coming enhancements.
  2. your CDI side is wrong, the point was to replace the SPI (which is never correctly impl @mp :() by an application scoped impl in CDI, it covers all cases but is rightly contextual, cached and matches all environments. In short: simple and works.

@jroper
Copy link
Member

jroper commented Jul 21, 2018

CompletionStage is for single values. For streaming, CompletionStage is not adequate. Please read https://github.com/eclipse/microprofile-reactive/blob/master/approach.asciidoc to get the full picture.

@rmannibucau
Copy link

I read it and understood it as an already outdated spec. Basically completionstage is for one time emittion (of one or multiple result values through composition), subscriber and friend of current java version solve the multi values point and you can evzn, as done in big data world, just do a stream mixed with completionstages in java 8 to not wait for it. So at the end the spec adds a lot of complexity and layer which will prevent other qpecs to not use this one for pretty much no gain.....and it still doesnt require this old spi since it is this topic

@kenfinnigan
Copy link

It doesn't address all the concerns of this issue, but during the Architecture call we agreed that SPI packages should be present in a separate SPI artifact and not present within an API artifact.

See https://wiki.eclipse.org/MicroProfile/ArchGuidelines

@kenfinnigan
Copy link

In today's architecture meeting there was agreement that this issue can now be closed

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Architecture board Issues across more MP specifications
Projects
None yet
Development

No branches or pull requests

6 participants