[Mod-Sys Reqs] Comment on "Substitution"

Peter Kriens peter.kriens at aqute.biz
Mon Jul 18 00:07:40 PDT 2011


Importing/exporting versioned packages gives you exactly what you need: full type safety, well understood by Java developers, and it already exists!

Using Jigsaw modules to define the API/Contract creates is awkward because it can only have one module per deployment unit. As we've some experience in this model I can assure you that that constrain will become painful.

Anyway, you're basically rediscovering OSGi's service model ...

Kind regards,

	Peter Kriens

On 15 jul 2011, at 21:15, Jesse Glick wrote:

> http://openjdk.java.net/projects/jigsaw/doc/draft-java-module-system-requirements-12#substitution is clearly necessary for the modularization of some legacy code, including existing APIs defined in the JRE. But I think this mechanism should not be considered the recommended practice for new APIs.
> 
> Current practice in the Java platform would result in an API module like the following:
> 
> module java.util.concurrent {export java.util.concurrent.*;}
> public interface ExecutorService {...}
> public class ThreadPoolExecutor implements ExecutorService {
>    // possibly buggy or slow impl coupled to other parts of Oracle JRE...
> }
> public class Executors {
>    public static ExecutorService newCachedThreadPool() {
>        return new ThreadPoolExecutor(possiblyPoorDefaultBehavior);
>    }
> }
> 
> This module mixes together an API - the public signatures and Javadoc - with a particular implementation. If you wish to use a different implementation (or an entirely different Java impl), you would be using another module
> 
> module my.concurrent {export java.util.concurrent.*;}
> public interface ExecutorService {...identical...}
> public class ThreadPoolExecutor implements ExecutorService {
>    // same signatures, but different impl
> }
> public class Executors {
>    public static ExecutorService newCachedThreadPool() {
>        return ...who knows...
>    }
> }
> 
> and then the special module system feature would permit my.concurrent to declare that it substitutes for java.util.concurrent.
> 
> This is workable, but awkward. How do you verify that my.concurrent and java.util.concurrent have precisely the same public signatures? Some sort of special tool could be employed to do so, but does the module system double-check this at installation time? Should the author of my.concurrent copy Javadoc comments from OpenJDK sources? Which module should a client declare when compiling against this API? If the API is evolved over time, so that java.util.concurrent 1.1 introduces some new methods, does my.concurrent need to declare that it substitutes for some version range of java.util.concurrent?
> 
> Now suppose most parts of a big modular application were originally tested and tuned against java.util.concurrent, but others rely on obscure bug fixes specific to my.concurrent, so you wish to load both implementations in the app. If there is anywhere in the app's common infrastructure APIs that refers to ExecutorService, you may be in trouble - the infrastructure will load java.util.concurrent's copy of the interface, but then some parts of the app would not be able to link against it, even though they are able to call my.concurrent's version of Executors.newCachedThreadPool for their own purposes.
> 
> Furthermore, suppose some of your unit tests for code compiled against java.util.concurrent wish to create a mock ExecutorService that runs all its tasks synchronously, or in lockstep with instructions given from the test, so as to behave deterministically. They can do so only if the tested code refrains from constructing an ExecutorService itself.
> 
> This is all a mess. The alternative is to declare the API using only interfaces:
> 
> module java.util.concurrent {
>    export java.util.concurrent.*;
>    requires service java.util.concurrent.Executors;
> }
> public interface ExecutorService {...}
> public interface ThreadPoolExecutorBuilder {
>    ThreadPoolExecutorBuilder threadFactory(ThreadFactory);
>    ThreadPoolExecutorBuilder corePoolSize(int);
>    interface ThreadPoolExecutorHooks {
>        void beforeExecute(Thread, Runnable);
>        void afterExecute(Runnable, Throwable t);
>        void terminated();
>    }
>    ThreadPoolExecutorBuilder hooks(ThreadPoolExecutorHooks);
>    ExecutorService build();
> }
> public interface Executors {
>    ThreadPoolExecutorBuilder threadPoolExecutorBuilder();
> }
> 
> This actual module, in a particular version and with authoritative Javadoc, with the most liberal possible license, can be published by the JSR spec lead as the API and used as a compilation dependency by all clients; no special module system directive is needed for that. The Oracle JRE would also include
> 
> module sun.java.util.concurrent {
>    require java.util.concurrent;
>    exports service java.util.concurrent.Executors class SunExecutors;
> }
> class SunExecutors implements Executors {
>    // the typical impl
> }
> 
> and similarly for my.concurrent, which now need do nothing special to ensure that it complies with the API signature other than be compilable. (It is also impossible to accidentally expose "bonus" APIs from my.concurrent, since it exports no types.) Client code would use a service locator or dependency injection rather than constructors or static methods:
> 
> module my.stuff {require java.util.concurrent;}
> class HeavyComputation {
>    @Inject Executors executors; // see previous mail
>    public void startRunning() {
>        executors.threadPoolExecutorBuilder().build().submit(....);
>    }
> }
> 
> The definition of ExecutorService can be shared among all modules in the app, even if some of them use different implementations of Executors internally (say, using @Qualifier). HeavyComputationTest can easily insert a MockExecutors which uses some sort of cooperative multitasking rather than actual threads.




More information about the jigsaw-dev mailing list