#CyclicDependences should be re-opened
David M. Lloyd
david.lloyd at redhat.com
Mon Feb 27 13:43:25 UTC 2017
I propose that the #CyclicDependences issue should be re-opened.
This issue is primarily about modules which exist on the module path in
a regular user application. We have observed that it is quite possible
for applications (even simpler ones) to require circularity in common
cases. It has been posited that ideologically, circularity is an
anti-pattern. However I find this position to be meritless, and
experience shows that in practice, this position causes more problems
than it solves.
The notion that all modules in a cycle are logically one is also flawed.
It makes the assumption that all the modules were developed by the
same person, at the same time, which is rarely if ever the case.
Modern, real world applications, even relatively small ones, now consist
of dozens or hundreds of distinct pieces from dozens of sources. Maven
is a big part of this: by allowing application dependencies to be
managed automatically, the friction of doing so has been greatly
reduced, and I've correspondingly observed that including substantial
dependency graphs in an application as a common practice has increased
Maven forbids circularity during compilation. This circularity
prevention extends to cycles in the artifact graph being built, and
building does not necessarily require the full graph to be present.
There is nothing today preventing cycles at run time, and many, many
users are taking advantage of this, either knowingly or unknowingly
every time they use their favorite containers and frameworks.
Anecdotally, it took us fewer than four months from our initial JBoss
Modules prototype in 2010 to discover that without circularity support,
we were going to have a very hard time assembling even a basic
application module set let alone our entire application server.
The primary JPMS remedy for circularity is services. In some cases this
can work, but there are limitations that prevent this from being
sufficient as a general solution, particularly for existing cases.
The problems come in multiple forms. We encountered the following two
before we abandoned the idea of restricting circularity as a mistake:
1. A module is compiled against an API (often a spec API); at run time,
a different API module is in use which consumes common code which in
turn comsumes the module being compiled. From the user perspective,
they are not even aware that they have introduced a cycle, and will be
surprised and often baffled by the problem at run time. This is the
most common case, and services cannot provide a complete solution,
particularly with existing code. Even if they could, avoiding cycles in
this sort of situation can require extreme care and planning.
2. An API module is designed to be linked statically against a flexible
implementation, by providing an ABI which must be satisfied. The
implementation must consume the API in turn; without circularity, the
two must be joined as one module. Even if somehow they did not, it's
typical for implementations to consume libraries that in turn require
the original API module. This is the case with slf4j for example, which
is generally regarded to be a better solution than its commons-logging
predecessor which did things in an arguably more JPMS-friendly way, yet
with critical disadvantages.
Neither of these cases constitutes a poor development practice, and in
fact they often afford quite a lot of flexibility of the type which Java
has previously excelled at.
It is possible for containers to enable circularity in a limited context
by bypassing the JPMS resolver. This is not a good solution, it is
merely the minimum solution. It leaves non-container users in the cold
and will lead to stressful late nights for developers who are trying to
assemble applications but are foiled by a restriction that has no
benefit to them and that they may not even understand. And,
in-container modules which can manage circularity will, when examining
their own module's descriptor, see incorrect information as at least one
module in the graph necessarily must not explicitly reference its
dependency (as a practical matter, it is difficult to come up with a
system wherein only some modules are not expressed so the simple
solution is to express none instead). A value-added feature is one
thing, but requiring a container for basic common-sense functionality
(incomplete as it would be in this case) is too much pain to inflict on
users for a flawed ideology used to justify an implementation choice.
I think that cutting off these use cases as an anti-pattern is poorly
justified and is not in the best interests of the community or the
More information about the jpms-spec-experts