Add multiplicity declaration to use clause of service consuming module
Stefan Dollase
stefan.dollase at rwth-aachen.de
Sat Mar 26 02:45:57 UTC 2016
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
Stefan Dollase
More information about the jigsaw-dev
mailing list