To be Reviewed By: August 27th, 2020
Authors: Patrick Johnson
Status: Draft | Discussion | Active | Dropped | Superseded
Superseded by: N/A
Related: ClassLoader Isolation
Problem
Many of Geode's sub-projects depend on one another. In the world of ClassLoader Isolation, JBoss Modules supports dependencies between modules by linking them to each other when they are loaded. To link modules, we explicitly define which modules a given module depends on when we define it. While this works for most relationships, it is insufficient for some types of bidirectional dependencies (A dependents on B and B depends on A). Explicit bidirectional dependencies, where A declares that it depends on B and B declares it depends on A, while supported by JBoss, are not allowed by Gradle–it would complain about them when building. What we see instead is something like an SPI model where module A depends on module B because B defines an interface that A implements and B doesn't explicitly depend on A, but assumes that A is on the classpath when trying to load implementations of the interface.
An example of this is logging; geode-log4j explicitly depends on geode-logging, and geode-logging, while it doesn't state it, requires geode-log4j to be on the classpath. Normally, these kinds of relationships are fine because everything is on the classpath, but when modularized, modules like geode-logging won't be able to access modules like geode-log4j that they don't explicitly depend on. We need a way to resolve these bidirectional dependencies to allow modules to access the resources they need.
Anti-Goals
This proposal is only intended to solve the problems of allowing bidirectional dependencies between modules and is unconcerned with...
- Breaking existing bidirectional dependencies.
Solution
The proposed solution is to introduce a linking module, which will contain no code and instead consist only of dependencies. This linking module will depend on every other module and every other module will depend on it, as shown below.
This creates a path between all modules because any module can access any other module via the linking module, allowing bidirectional relationships to be resolved. This will have a similar effect to having everything on the classpath but prevents conflicts because modules will look inside themselves first and only look outside when they reference a type that they don't define or are explicitly loading implementations of an interface.
The linking module will link to other modules by creating/recreating itself each time a module is loaded/unloaded. All modules will be made to depend on the linking module when they are created. The linking module will then unload and unregister itself, get the updated list of loaded modules, and reregister itself with dependencies on all current modules. Because the linking module's name stays the same, the modules that depend on it will still be able to find it after it refreshes itself.
Use Cases
Scenario 1: Module A explicitly depends on module B.
Expected behavior before modularization: Module A finds module B because module B is on the classpath.
Expected behavior without linking module: Module A finds module B directly because it depends on module B.
Expected behavior with the linking module: Module A finds module B directly because it depends on module B.
Scenario 2: Module A explicitly depends on module B. Module B defines an interface that module A implements. Module B loads implementations of its interface expecting to find the implementation(s) from module A.
Expected behavior before modularization: Module B finds the implementation(s) in module A because module A is on the classpath.
Expected behavior without linking module: Module B fails to find the implementation(s) from module A because module A is not on the classpath and there is no path from module B to module A.
Expected behavior with the linking module: Module B finds the implementation(s) by going through the linking module to get to module A.
Scenario 3: There is no explicit dependency between modules A and B in either direction. Module A tries to load a resource from module B and/or module B tries to load a resource from module A.
Expected behavior before modularization: Modules A and B find what they need because both are on the classpath.
Expected behavior without linking module: Both modules A and B fail to find what they need because there is no path between them.
Expected behavior with the linking module: Both modules A and B find what they need from each other via the linking module.
Changes and Additions to Public Interfaces
No changes to public interfaces.
Performance Impact
No anticipated performance impact.
Backwards Compatibility and Upgrade Path
No backward compatibility impact.
Prior Art
An alternative would be to break all bidirectional dependencies between sub-projects, such as between geode-logging and geode-log4j.
FAQ
Answers to questions you’ve commonly been asked after requesting comments for this proposal.
Errata
What are minor adjustments that had to be made to the proposal since it was approved?