An optional service is something that could be considered, but I think there’s 
a difference in expectations that underlies this entire discussion.

Java’s classpath offers a very dynamic model of program configurations where 
class resolution is *expected* to possibly fail at runtime. This has some 
disadvantages but also some clear advantages. Modules are not meant to continue 
the same model by other means, but to offer a very different one, with its own 
pros and cons. Dialing back Java’s dynamism is their point, and in more ways 
than one (both strong encapsulation and reliable configuration are about 
restricting dynamism in favour of more static guarantees).

When you configure the application on the command line you already know whether 
or not you wish to include a module that contains the service interface in the 
configuration. If you don’t, then don’t include modules that provide that 
service. There is no need to “work around” the lesser dynamism. That is a 
different experience from the classpath’s model of “I’ll throw some stuff in 
there, maybe it’s needed maybe it’s not; maybe it’s consistent, maybe not,” but 
it is different by design.

In particular, `requires static` — intended for compile-time dependencies — 
also doesn’t work like the classpath. If you happen to reference a class that 
exists on the class path it will be resolved, but `requires static` is 
insufficient to resolve the module. Which makes me wonder, what is the root of 
the optionality in your code? I.e. how does io.avaje.inject come to be resolved?

— Ron

On 18 Apr 2023, at 11:50, Rob Bygrave 
<robin.bygr...@gmail.com<mailto:robin.bygr...@gmail.com>> wrote:

> `requires static` is more for use-cases like annotations that do not need to 
> be present at run-time

I am aware that annotations have a retention policy but otherwise why are we 
specifically saying "like annotations" here as opposed to just saying "types"?


> I understand there is a temptation to compare `requires static` with optional 
> dependences in Maven but they are not the same thing. `requires static` is 
> more for use-cases like annotations that do not need to be present at 
> run-time. It could of course be extended but it's a slippery slope that 
> ultimately amounts to giving up on reliability.

Background:  I also maintain Ebean ORM which is made up of 20+ modules (+ 3rd 
party dependencies) and works with classpath and module-path. A quick search 
for "requires static" there shows me 25 uses of requires static (192 requires 
clauses in total on the core modules, I'm suggesting this is a non-trivial use 
of module-path that uses a decent amount of requires static).

Of those 25 requires static, 5 of those are dependencies on optional 
annotations and 20 are optional dependencies that are NOT annotations. They all 
match to maven optional true dependencies (self fulfilling I know).

For all these cases requires static has worked exactly as I expect and exactly 
the same with classpath and module-path. There have been no issues.  No issues 
when the requires static was for annotations and no issues when the requires 
static was for normal types (not annotations).


> do not need to be present at run-time.

So for ebean orm, it is using [requires static / optional at runtime] types 
that are both annotations and normal class types and they are all potentially 
not present at run-time. There is a suggestion there is a slippery slope for 
the requires static types that are not annotations? I'm wondering why? As in, 
optional dependencies have been reasonably extensively used and there has been 
no issue hit here with module-path and requires static and everything has 
worked as expected*.

*Except the issue noted in this thread which imo isn't an issue with requires 
static. That is, I'm now aware of the issue with ServiceLoader in module-path 
not working when provides p.S is via requires static (unlike classpath) ... but 
in my mind that issue isn't really an issue with requires static per say but 
instead it is an issue with the runtime module resolution (by not allowing 
provides p.S to be optional).


> It could of course be extended

Extended? What is wrong with how requires static works now? How are you 
suggesting it could be extended and for what purpose?


> but it's a slippery slope that ultimately amounts to giving up on reliability.

Well, optional dependencies by their nature are sometimes dynamically 
determined at runtime which isn't ideal but also not difficult or new. Can you 
give an example of what you mean by this?


Thanks, Rob.

On Tue, 18 Apr 2023 at 20:05, Alan Bateman 
<alan.bate...@oracle.com<mailto:alan.bate...@oracle.com>> wrote:

On 17/04/2023 15:20, Rob Bygrave wrote:
:


> is it reasonable to consider that ServiceLoader is the *only* vector by which 
> the implementation class will be instantiated?

In my view this is expected and imo I get there by thinking in the opposite 
direction from the provides p.S type to the requires static rather than the 
other way around. That is, the p.S type is only available via requires static 
hence it is expected to potentially not exist at runtime in the module-path. 
That is, if p.S was expected to exist at runtime it would be "read" via a 
requires or requires transient clause and not via a requires static - the use 
of requires static for this case is explicit and intentional.

In using requires static ... imo we are explicitly going out-of-our-way to say 
"the types here might not be available at runtime" and the classic case for 
this as I see it is this case of providing an optional service, that will only 
be service loaded if the user of that service is in the classpath / 
module-path. IF the module that is the user of a service is in the classpath / 
module-path then that module will ensure that the p.S type is in the 
module-path.


In your example, module io.avaje.config declares that it provides an 
implementation of io.avaje.inject.spi.PropertyRequiresPlugin. There is nothing 
to connect this to `requires static io.avaje.inject`. The module system would 
need to search "far and wide" for io.avaje.inject to see if exports 
io.avaje.inject.spi to io.avaje.config, otherwise there is no way for it to 
know that the "missing package" is in a module that is not required to be 
present at run-time. In other words, `requires static io.avaje.inject` does not 
convey to the module system that io.avaje.inject exports packages with service 
types.

I understand there is a temptation to compare `requires static` with optional 
dependences in Maven but they are not the same thing. `requires static` is more 
for use-cases like annotations that do not need to be present at run-time. It 
could of course be extended but it's a slippery slope that ultimately amounts 
to giving up on reliability. In this case, it amounts to giving up on a post 
resolution check and allowing all service providers to have a dangling 
reference to a service type.

-Alan

Reply via email to