rbygrave / skeleton-optional-service

Apache License 2.0
0 stars 0 forks source link

There are really good advantages to having three jars. #2

Open agentgt opened 1 year ago

agentgt commented 1 year ago

Hi Rob,

You are probably not looking for commentary on this but apparently I follow you and this popped up on my github recs... so here we go based on my experience:

I understand and agree multiple artifacts are painful particularly in the context of an application going full dependency inversion instead of a library. It is painful in almost any IDE to see 3 projects instead of just 1 (albeit Eclipse IMO much better than intellij on this because of its "Working Sets"). This is largely a waste because although the separation particularly compile time separation is good it is unlikely there will ever be multiple services/plugins.

However it should be noted in the context of libraries there are multiple implementations. Not just multiple implementations but possibly implementations that extend others.

The last sentence is important.

Let us take logback as an example. Let us say I want to make a new plugin SLF4J that extends logback. You cannot do it today because SLF4J only allows one implementation and unfortunately the logback does not have its implementation separated from the actual service registration (module-info and META-INF/servces.

Thus based on my experience ideally a library that wants to allow extending (perhaps through inheritance) have their plugin jar ONLY have the service registration (and corresponding class service provider implementation).

module spec.a {
  exports spec;
  uses spec.SPI;
}

module lib.b {
  exports lib.b;
  requires transitive spec.a;
}

module lib.b.spec.a {
  requires lib.b;
  requires spec.a;
  provides spec.SPI with LibBSpec;
}

// Now my plugin that wraps lib.b

module myplugin {
  requires lib.b;
  provides spec.SPI with MyPlugin;
}

I guess what I'm saying is a problem with the ServiceLoader is that just by adding a jar you are opting in that it is the implementation but what happens if you just want to extend the implementation. Perhaps this is not a common use case but there have been many times I have wanted to wrap Logback but cannot.

rob-bygrave commented 1 year ago

Great comments and certainly raises points that I had not thought about - thanks for that!! I'll leave this issue open for other people who wander here to read etc.

For people who are not aware, this project was created to help with a discussion on the jigsaw-dev mailing list about this pattern and https://bugs.openjdk.org/browse/JDK-8299504 .How this pattern works in classpath, no longer works in module-path (hence the JDK bug logged), and what the options are for dealing with that for libraries that want to support both classpath and module-path).

I'll look to add a link to that jigsaw-dev discussion / thread some time.

Cheers, Rob.

agentgt commented 1 year ago

Oh yes I forgot about that problem. Was it brought back up? I have been unsubscribed lately from the jdk dev mailing list.

Another issue with the module-info.java that is apropo to our projects is you can't generate a listing of providers and concatenate them like you could with META-INF/services. Well that I know of.

The META-INF/services was an easy pseudo-non-reflection (it still is reflection but Graal VM native can use it as a hint as a reachable class) way to generate a list of generated classes. I suppose you bytecode could alter the module-info class file but that seems wrong.

One thing that module-info.java provides does have going for it (that few know about) is that you can use a static method called provider instead of no-arg constructor. This I suppose slightly mitigates the extra jar problem you as don't need a proxy/forwarder/decorator type implementation (that would then call the other jars real implementation as I'm fairly sure you can't provides ... with another class that isn't in your module... fairly sure).

rob-bygrave commented 1 year ago

Oh yes I forgot about that problem. Was it brought back up?

Yes, by me - https://mail.openjdk.org/pipermail/jigsaw-dev/2023-April/014846.html

you can use a static method called provider instead of no-arg constructor.

I only just saw that the other day. I'm wondering if that existed/works in Java 11 via classpath so looking to check that sometime. At the time it didn't look like a module-info only thing to me so I'll look to check that.

method called provider ... slightly mitigates the extra jar problem

As I see it no it won't help for this particular case. Ultimately the issue is that for provides <interface> with ... that <interface> type MUST be a class that exists and resolvable in module-path (unlike classpath where it is allowed to not exist). Ultimately, today this means that the provides ... clause needs to move into another jar so that it is in a different module - which then guarantees that for module-path the service implementation is only accessible if the service interface type is accessible.

Its a solution that is growing on me each time I look at it :)

A future Java might support multiple modules in a single jar but that sounds like it would be quite some before before it arrives and can be adopted by libraries.