Add multiplicity declaration to use clause of service consuming module
Hello EG, the other day, I sent this message to jigsaw-dev. However, I was told to send it to this list, because it is more appropriate. Quoting my original message: Hello,
I read the article in [1], which describes the problem that jlink cannot figure out which service providing modules should be included into the custom modular run-time image. In [2] there is a statement about this: Previously, jlink included all service providing modules. Now, none of them is included. Since I am not sure whether this was already discussed, I would like to suggest a third approach:
My suggestion is to add a multiplicity declaration to the module-info.java file for each uses clause. Here is an example:
module com.socket { uses one com.socket.spi.NetworkSocketProvider; }
This declares that the module `com.socket` requires exactly one implementation of the service `com.socket.spi.NetworkSocketProvider`. Here are further suggestions for multiplicity keywords: * optional - requires either zero or exactly one implementation * one - requires exactly one implementation * at least one - requires at least one implementation (this needs a better keyword) * any - any number of implementations is sufficient, even zero
I suggest to require a multiplicity declaration, so the intent is always clear. However, it will also work to choose a default when no explicit declaration is given. Currently, any is the default.
Adding a multiplicity declaration to the uses clause of the service consuming module allows to nicely solve the problem that is described in the article. Now, the jlink tool can check whether the multiplicity declaration is fulfilled. Then, it can include only the required service providing modules into the custom modular run-time image.
Besides this declaration, one might add convenience methods to the ServiceLoader class. Currently, there is only ServiceLoader::iterator. I suggest to add the following methods: * loadOptional() - checks that there is exactly zero or one implementation and returns an Optional<> containing a new instance of the implementation, if any, throws a RuntimeException or Error if there is not exactly zero or one implementation * loadOne() - checks that there is exactly one implementation available and returns an instance of the implementation, throws a RuntimeException or Error if there is not exactly one implementation * loadAtLeastOne() - checks that there is at least one implementation available and returns a Set of instances of the implementations, throws a RuntimeException or Error if there is not at least one implementation * loadAny() - returns a Set of instances of the available implementations
I think these methods should throw an Error, since the condition can and should be checked at compile-time. Thus, it is a fatal error if the constraint is not met at run-time.
Finally, it might be helpful to be able to explicitly declare which service implementations are provided to which service consumers. For example, given a service `S` with the implementations `S1`, `S2` and `S3` provided by the modules `M1`, `M2` and `M3`. `S` is used by the modules `SC1` and `SC2`. `SC1` requires exactly one implementation of `S` while `SC2` requires any number of implementations. Now, it is unclear which implementation should be used by which service consuming module. Currently, the service consuming module will decide this manually in the code by iterating over all provided implementations. I think this should be explicitly declared. For example, one might declare that for `SC1`, `S` is provided with the implementation `S1`, but for `SC2`, `S` is provided with the implementations `S2` and `S3`. The great thing about this explicit declaration is, that tools like jlink can use this information, so it becomes even more clear which modules are required at run-time. Provided that `SC1` and `SC2` are included in the custom modular run-time image, the service configuration given above implies that also `M1`, `M2` and `M3` have to be included in the custom modular run-time image. If the service configuration is changed so for `SC2`, `S` is only provided with the implementation `S1`, then only `M1` has to be included into the custom modular run-time image, but `M2` and `M3` can be omitted.
Also, my suggestion is that this is an interpreted document instead of a compiled one, so it can easily be changed after a release. For example, if an application uses a logging service it would be helpful to be able to add a custom logging service implementation to the module path and simply change the implementation which is used by the application by changing the service configuration file. This also implies that there should be a tool to validate the service configuration via the command line.
Here is a summary of the proposed changes: * add a multiplicity declaration to the uses clause * use the multiplicity declaration to validate the current configuration of service consumers and service providers * include all required service provider modules when executing jlink * add convenience methods to the ServiceLoader class which match the introduced multiplicity declarations * add an (interpreted) document to explicitly declare the service configuration * add a command-line tool to validate the service configuration
I am happy to hear any feedback. If you have trouble understanding my suggestions, please don't hesitate to ask for clarification.
Again, I am not sure whether this topic was already discussed. I was unable to find it. If it was, please feel free to ignore this mail and send me a link to the discussion.
[1]: https://blog.codecentric.de/en/2016/01/java9-jigsaw-missing-piece/ [2]: https://twitter.com/mreinhold/status/665122968851382273
Regards
Quoting the answer by Alan Bateman: On 26/03/2016 02:45, Stefan Dollase wrote:
Hello,
I read the article in [1], which describes the problem that jlink cannot figure out which service providing modules should be included into the custom modular run-time image. In [2] there is a statement about this: Previously, jlink included all service providing modules. Now, none of them is included. Since I am not sure whether this was already discussed, I would like to suggest a third approach:
JEP 282 [1] lists services as an open issue.
Yes, it was the case, at least for a brief period, that service providers were linked in automatically. It immediately leads to calls for ways to keep providers out and it gets very complicated once you have a command-line that it try to add and exclude modules at the same time.
So as things stand, jlink is an advanced tool and you do need to know about service providers when creating a custom runtime image. If you want specific crypto support or a specific dynamic language then you need specify the names of the modules to -addmods when creating the runtime image.
On extending the `uses` clause then this may be something to send to jpms-spec-comments for consideration if you feel it is important. One thing to be aware of is that the early exploratory phase of Project Jigsaw had `requires service S` and `requires optional service S`. The former could cause resolution to fail if there weren't any modules providing implementations of S. The latter is more like what we have today with `uses`.
At least for usages in the JDK, then we almost always used the latter in the prototypes at the time. One reason is the consumer modules often have a default implementation to use when there aren't any implementations deployed. The java.xml module has a default implementation of each of the XML parser types for example. There are a few cases, like the java.scripting or java.naming modules where there isn't any notion of the default implementation but it's not an error in either case to link or run without any service providers. The reason is that service provider modules may be loaded at runtime, maybe into module layers that a container creates.
-Alan
As I said before, I am happy to hear any feedback and provide clarifications. Also, if there was already a discussion about the topic, please point me in the right direction. Thanks. Regards Stefan Dollase
participants (1)
-
Stefan Dollase