The discussion around modularity within the Java community has lately been around the debate between OSGi and Jigsaw about what the right way to implement modularity is. Jigsaw bases their module on the Java class, while the OSGi module is based on the package. Peter Kriens has been a strong proponent of the OSGi's system of writing modular code. In this article, Kriens supports his argument by explaining that packages are to Java modules what interfaces are to classes: a type-safe way to minimize coupling between modules.
The more I read about Jigsaw the more I get the feeling that Jigsaw only wants to solve the relatively minor class path assembly problem. With applications that have hundreds of dependencies assembling the class path has become difficult and Jigsaw tries to automate it, hoping the pain will go away when the class path is called the module path. However, if the problem is class path hell (JAR hell, DLL hell), then simplifying the class path assembly is like curing cancer with morphine. It might temporary kill the pain but it does not solve the underlying problem.
Take Maven, it basically already solves the class path assembly problem in a similar dependency architecture as Jigsaw. In a demo at JavaOne, the Jigsaw crew actually used Maven to automatically create Jigsaw modules but found out that they had to fix the dependencies because these were wrong or pointed to incompatible versions with respect to the module path.
It is not that Maven is bad or that the artifact developers are stupid, it is just that this particular module-to-module dependency model significantly increases coupling due to its aggregate and transitive nature. This dependency architecture is the underlying cause behind it being hard to prevent maven from downloading the Internet. This dependency model is worse than morphine, this pain killer actually worsens the underlying coupling problem due to its dependency aggregation.
Interestingly, this aggregate/transitive dependency problem is an old software problem in disguise. Classes had the exact same problem before Java came around in 1996. Trying to include a class from a large application invariably dragged in a whole load of other classes that dragged in more classes, ad infinitum. This problem is very well described as the big ball of mud by Brian Foote.
The root cause is that an implementation class aggregates a number of dependencies to simplify its construction; it justly uses libraries to ease its construction. However, these implementation dependencies become a problem when the class is part of a (long) transitive dependency chain; then it invariably drags in much, much more than is really needed by the original class that was being reused.
Every Java programmer is intricately aware of the solution: interfaces. Interfaces minimize coupling by breaking the dependency between implementation classes and client classes, the buck stops at the interface. Interfaces were specifically introduced to break the transitive type coupling that plagued object-oriented software so much in the nineties. Interfaces really made our class diagrams look much more uncoupled and were leveraged to the utmost in the inversion of control systems that are so popular today. Interface based programming is arguably one of the best practices in Java.
So if a Jigsaw module resembles an implementation class then what is it the module analog for an interface? The current Jigsaw documents have the module provide statement planned for this role. Its use case is that a module can provide basically any one thing. In the proposal it is only a name and a version and all its semantics are implied by that name without any enforcement of the contract.
The primary use case for provide is that IBM can say they provide JDK 7 in their version of the VM. A name only is a (very) poor man’s analog to an interface since it ignores the Java type system; there is no way to verify this claim by the compiler and VM on the implementation and client side. If interfaces were made like this they would not contain any methods, they would just be a name. For example Runnable would not specify the run() method. Just saying that you implement Run would satisfy the then very trustworthy compiler that you did. If a client called run() the same compiler would just believe that the client knew what it was doing. Though it would be significantly less typing, would it still be Java?
For Jigsaw modules can’t we do better than to just name the contract? Is there a solution that uses the existing Java type system for verifying that a module provides what it promises? Just like what interfaces do for classes?
So what is an interface, really? The key aspect of an interface is that it abstractly describes a part of the implementation class so that the implementation class is then free to use whatever it needs to simplify its implementation work. Its external contract is therefore not implying the aggregated and transitive dependencies of the implementation since multiple implementations can exist with different dependencies. An interface is a named set of methods that is then used to verify the contract between implementation and user.
When we focus on modules, the interface itself is of too low a granularity for this role because a module is of a larger granularity than a class. Instead of a set of methods (the interface) a set of types is a more natural contract for modules because it allows modeling the different roles (the servers, event listeners, domain types, etc.) in a software contract. A named set of types is therefore a natural granularity for a type-safe specification contract between modules.
Surprisingly, Java already has the concept of a named set of types, it is called a package. The package shows itself to be a natural fit for a module contract because it is already used as a specification contract in many JSRs and open source projects. Best of all, packages are already completely integrated in the Java type system. An indirection that gives the needed decoupling and flexibility to construct today’s software systems.
In the package based dependency model a module can provide a number of packages and it can consume a number of packages. Just like classes can implement a number of interfaces and use a number of types. Packages provide the same benefits for the module as interfaces; they allow a module to optimize implementation choices without affecting the contracts it provides.
OSGi provides both the direct module-to-module dependency (Require-Bundle) that Jigsaw uses but also the interface-like light coupling that packages (Import-Package) allow. Long term OSGi users are so used to the advantages of Import-Package and so aware of the long term damage of Require-Bundle that a certain amount of zealotry might have crept into the discussion.
The frustration is that we know from many years of experience that package imports work much better than requiring bundles in an evolving system. Unfortunately, we lacked the language to explain this to people that had less experience with this type of modularity. People that learned Java after C(++) might remember how strange interfaces looked when they first saw them, new concepts can be hard to explain. Only recently did it became clear to me that packages actually perform the same decoupling role that interfaces play for classes; this now provides a language that can be used to explain more of OSGi to all Java users.
So if too much zealotry has gone into the Java modularity discussion then I beg forgiveness. But be honest, how would you feel if someone suggested to remove interfaces from Java?