*> source of the problem:* I'll have a crack.
As I see it, I think this case is that some modules have "split personalities" in that they don't exist to provide a service as their *main goal* but it happens that they can also additionally provide a service IF they are used in an app configuration that uses and loads implementations of that service [and that service implementation would be left unused if it is used in a standalone configuration that does not include the service interface]. Y (service provider) has a main goal of doing "Y good stuff". It really does not want a *hard* dependency to X, it can be used by itself completely standalone but IF it is used in an application configuration that includes X then ... it additionally happens to provide an implementation of an X service interface. Some people might call this "An optionally provided service" that might not be loaded. e.g. Module X has an interface x.spi.Plugin and will service load all the implementations. Module Y exists to do "Y good stuff", exports y; and can be used completely by itself standalone. An application configuration that only includes Y is good and expected. Module Y can also (as a secondary reason to exist) implement x.spi.Plugin to provide useful functionality to an application configuration that includes Y + X. module y { exports y; // Main reason to exist // As a secondary reason to exist requires static x; provides x.spi.Plugin with ... <internal implementation> } Application configuration using just Y standalone fails at startup based on the existence of "provides x.spi.Plugin". *> if they choose not to use X, Y would just sit there, unused.* Y still provides its main reason to exist, it's "Y good stuff". What is not used is that 1 class which is the implementation of x.spi.Plugin (and with module-path this implementation can be hidden / not exported and not open to abuse / accidental usage - yay!!). Fixing a module with a split personality: ----------------------------------------------- We could split the Y module into 2 modules - Y and Y-X-Plugin. The only classes in Y-X-Plugin are the implementation of x.spi.Plugin + module-info with the service loader configuration etc. Now we have 3 application configurations to consider: - Y (now works) - X + Y (might be confusing for users) - X + Y + X-Y-Plugin (needs documentation) What is mooted is to be able to just have the 2 configurations to consider: - Y (standalone) - X + Y (y provides its "Y good stuff" + acts as a x.spi.Plugin for X) The reason why this could be considered acceptable is because the implementation of x.spi.Plugin that would exist in Y can be hidden / not exported and not open to abuse / accidental use. Cheers, Rob. On Wed, 19 Apr 2023 at 04:05, Ron Pressler <ron.press...@oracle.com> wrote: > > > On 18 Apr 2023, at 16:01, Josiah Noel <josiahn...@gmail.com> wrote: > > On Tue, Apr 18, 2023 at 8:46 AM Ron Pressler <ron.press...@oracle.com> > wrote: > >> >> 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? >> > > So avaje jsonb/config/http implements SPI interfaces exported by avaje > inject(which is added as a maven optional dependency). The idea here is > that the plugin implementation would be loaded by avaje-inject to add to > the DI scope. > > Outside of avaje inject, these service implementation classes have no > meaning and are not meant to be instantiated. In some cases, the service > implementation package may not even be exported by the module, so even if > you tried you couldn't instantiate outside of a service loader. > > Does this help answer your question? Or did I misread it? > > > I think so, thank you. But when the application runs in a particular > configuration, the application deployer knows whether or not that > configuration uses the avaje inject module. If it does not, why is the > configuration including the service module? > > The class path has a loosy-goosey attitude, but modules are all about > creating a proper configuration. Ideally, there shouldn’t be any modules > that could not possibly be used. > > We could discuss making modules more laid back in some specific > situations, but generally, they exist so that we could have a strict > configuration. So I guess my question now is, why would you want to allow a > particular application configuration to include a module that could not > possibly be used in that configuration? > > I assume the answer is to avoid the need to document to the user that if > they choose to use some capability offered by module X (avaje inject) they > should also add module Y (the service provider). If we were more lenient, > you could just give them module Y, and then all would work whether or not > they choose to use X. But the flip side of that is that if they choose not > to use X, Y would just sit there, unused. Best case scenario, it would just > increase their image size; worst-case scenario is that it could preclude > some future optimisations that may require a full program analysis. > > To do this properly, as Alan alluded, `requires static` is insufficient > because the module doesn’t know where the service interface is supposed to > come from. Rather, we would need a new specific mechanism that says “if > module X is readable, then provide this service”. To know how important > such a feature is we need to understand the source of the problem: why is > it hard to exclude unused modules? > > — Ron >