The Source for Java Technology Collaboration
User: Password:



Patrik Beno

Patrik Beno's Blog

JSR 277 Review

Posted by patrikbeno on October 24, 2006 at 05:15 PM | Comments (17)

It took me a while to write something up :-)
Hope the formatting does not matter...

Also published here.


JSR 277 Early Draft Review

JSR 277 Early Draft Review

JSR 277: Java Module System

Patrik Beno


Copyright © 2005 Patrik Beno

Revision 160, saved on 25/10/2006 02:30


Revision

Changes

Date

Author

159

Initial draft (published)

25/10/2006

Patrik Beno

160

Some typos fixed

25/10/2006

Patrik Beno



This document is an attempt to review and comment work of the expert group presented in the initial early draft of the specification published on October 11, 2005.

I assume readers are familiar with the JSR 277 (and therefore I will not explain or quote it more than it is necessary).

My impressions are three‑fold:

Ÿ           There is some great stuff in this JSR

Ÿ           Some stuff is missing

Ÿ           And finally, I see some design flaws, some of them real hard showstoppers.

Now if you’re ready, read on.

2.1             Redefined Class Loading Model

One of the most important statements of this JSR is the fact that it actually obsoletes and deprecates current class loading scheme.

Original class loading model is a hierarchical view on software layers, which is inherently wrong and insufficient when it comes to component‑based architectures. For a long time this model crippled our natural dependency modelling attempts and approaches, ever since the first IDE introduced module/project dependency management features.

The runtime deficiency bothered us with an impedance mismatch as we struggled with various sort of silly problems like class loader isolation, unsatisfied and/or conflicting dependencies and so on. Various different concepts were introduced in order to help us at least partially solve or workaround these problems (some of them both funny as well as dangerous, let me mention at least the infantile parent‑last classloading scheme).

This JSR finally has its chance to fix these problems by introducing the runtime model which is real native directed graph which natively supports true component architecture (at least from dependency management perspective).

2.2             Support for Concurrent Module Versions

Due to redefined class loading model, different versions of the module can be loaded by the module runtime system. Classloader per module is a smart decision that allows this. The problem itself is not that simple at all but the good news is that the model does allow this. (Bad news is it does not specify how exactly this would be supported. Read later on in section “Flaws”.)

2.3             Repositories

You know Maven repositories and how they work, how they are linked together? Now imagine module system can do all and give it to you this at runtime.

This, I think, is effectivelly death of CLASSPATH, WARs/EARs etc.

Repository Delegation

Actually, this feature makes repositories the successors of the old‑style classloader delegation model. And this is where this model does really make sense.

2.4             Dynamic Behaviour

To highlight this without much talking, I really appreciate dynamic import policies (section 2.7.2) which provides to programatically configure module dependencies when the built‑in declarative approach is not enough for your problem.

While this may introduce some serious implications on manageability of such dynamic imports, I think this is great feature, just because that’s what software should be: dynamic interacting pieces (I’ve never really liked any “final” models or designs).

3.1             JSR 294 (Improved Modularity Support)

Just for the record, full review of this JSR (277) is not possible unless all its dependencies are fixed and known (JSR 294). Last time I checked, there was not too much to be seen there.

3.2             JSR 291 (Dynamic Component Support)

I have not yet reviewed JSR 291 but I think it (especially its module system) may be a huge overlap with this JSR (277). I don’t understand why there is no sign of any cooperation between these expert groups (not counting single document reference in chapter 10 of JSR 277 EDR).

3.3             Runtime Requirements (Expectations)

Module declares dependency on API (e.g. JMS or JDBC) but does not specify any runtime implementation. However, it may not work properly without the expected contract to be fulfilled at runtime.

I would expect JSR such as this one will

Ÿ           define how the runtime expectations like this would be declared (expressed)

Ÿ           define how how these expectations should be resolved at runtime

Maybe the note C.4 (appendix C) is supposed to refer to this problem.

Understanding the Difference

While static dependencies are satisfied via module class loader hierarchy, runtime dependencies are resolved with respect to runtime context (context classloader).

3.4             Compilation with Modules

JSR does not specify how the module-aware compiler should compile the compilation unit (module sources)

When some compilation unit (development module) is compiled, the compilation classpath (to put it in the old terms) is composed of

Ÿ           Module direct dependencies

Ÿ           Plus the dependencies exported by these direct dependencies

3.4.1 Persisting Actual Dependencies Used for Compilation

Compiler uses dependency imports and version constraints to compile development module. The resulting artifact (JAM) should include exact version numbers of the dependencies that were used to build the given module.

This is required for future reference when potential runtime problems (conflicts) need to be resolved.

3.5             Lifecycle

JSR does not seem to see a module as a dynamic component with its own lifecycle

Ÿ           Init() - Module is initialized (module as a software component, not just module definition)

Ÿ           Start()

Ÿ           Stop()

Ÿ           Destroy() – unload module when it is not used

Again, C.6 is a closest note related to this issue.

(No particular order!)

4.1             Version Conflict Detection and Resolution

Sample scenario 8.2.3.3 describes situation when two different module versions are loaded.

This may or may not introduce a class incompatibility to the whole system. It may or may not be regular working deployment. It strongly depends on the actual software architecture and the object/type flow.

The early draft does not offer any solution to this problem. It does not even mention this issue at all. This is not an easy problem at all but unless the JSR provides any reliable solution, it should avoid introducing or proposing any features that could possibly break the runtime compatibility and type safety.

In the mentioned scenario, modules B and C may safely use different version of module D (say, some xml parser library) if they use it internally only and they do not share instances they create.

However, if they share such objects (like returning parsed XML document to module A), there’s nothing that can prevent the module A from trying to pass such instance to module C where the class incompatibility causes an error. In fact, such error may occur virtually anywhere on the object’s way from B via A to C and D.

Understanding the Problem

The problem is that (a) while module dependencies specify mostly direct type interactions between modules, (b) type isolation is determined by data (instance) flow between software layers.

There is a whole lot more to type isolation management than class loader isolation.

If this problem can be practically solved at all (and I believe it is because software is layered), the module system must impose a set of architectural restrictions on modules and provide some kind of managed environment for them. The limitations of a contrained managed environment may be too restrictive to make this practically usable.

This is a very complex issue that needs much deeper analysis.

4.2             Granularity

Robust system need to choose level of granularity on which it will operate and hold to it. It has to introduce an entity for which contracts and relashionships are defined. Such entity might constitute other higher‑level entities if needed but this entity should not be broken down to smaller pieces.

As for the module system this JSR is trying to define, I believe this entity is obviously a module itself.

4.2.1 Class/Resource Exports

Class/resource exports violate the idea of module system and break down the newly defined entity (module) back into the independent pieces of bytes (classes/resources).

Class exports break the granularity rule mentioned above. For module system itself, there is no such thing as class or resource. The only thing that matters is the module.

The module should not be required to explicitly export fragments of its internals (classses or resources). It is not transparent, it is error prone and most important, and it violates the principal idea of module system.

Module may or may not export its dependencies (other modules). However, if module gets imported, it is imported as a whole, no exceptions.

If module needs to hide parts of its internals (like private API implementation), it may export them into separate module and import such a module as a private unexported dependency.

This requirement may seem too restrictive at first sight but it just encourages proper module‑based architecture without the backdoors and hacking or breaking down the elementary building blocks.

4.2.1.1 Drawbacks

Incomplete exports

// not exported

class A {

}

 

// exported

class B {

void doSomething(A a) {}

}

In the above example, class B is exported by the module but class A is not which makes the class B actually unusable (I may be able to access class B but I am unable to call method doSomething because the required type is for me unvisible)

Various use cases – various exports applicable

// exported

Interface API {

}

 

// not exported

Class Impl implements API {

}

It sounds like a good idea to export only API and hide the implementation classes. At least form the regular client point of view.

But what if you want to extend the default implementation and plug it into the particular framework runtime? The Impl class is not accessible because it was hidden (not exported) by the module developer who thought it was a good idea to expose only API classes.

With explicit class exports, we’ll sooner or later end up with default export‑all policy applied throughout the all modules. The feature will be deliberately bypassed to make the module actually globally useful and avoid crippling its interoperability potential.

Class exports are doomed to meet the very fate of a ‘public’ keyword – redundant piece of highlighted crap that needs to be added everywhere to make all the stuff really useful across packages…

4.2.1.2 One Last Note

If JSR 277 expert group is not willing to abandon the idea of class/resource exports, at least the approach should be reversed: use explicit DENY instead of explicit PERMIT.

There are different approaches to securing different systems. These differenciens are pure optimizations for a majority use case. Careful selection of the suitable approach is a key to better manageablity and usability of the security model.

Wrong decision sooner or later causes the users (administrators) to deliberately bypass or weaken the system which in turn fails to satisfy security requirements.

Attempt for an illustrative example: If I am to secure CIA building or a computer exposed directly to an internet, I choose the strategy of implicit global DENY followed by explicit adressed PERMITs.

If the subject to be secured is a public park, library or a collaborative web forum, more useful (practical) approach is a global PERMIT system with the exeptions expressed via explicit DENYs.

I hope I did enough to make my point.

4.3             Cyclic Dependencies

I thought no one can ever really consider idea of actually encouraging and supporting cyclic dependencies between modules, yet here we are, JSR 277 really enjoys this masochistic nonsense (see 8.1.2, 8.3.4).

I cannot see any real‑life use case where cyclic dependencies are really needed. All temptations to introduce cycles in the dependency graph can be successfully supressed by proper modularization.

Cyclic dependencies actually break the original promise of the modularized system with clean, transparent and manageable dependencies. They are hack to allow bad architects to workaround their modularization issues.

Now let me make myself clear:

It is with no doubt useful and necessary to have support for cyclic dependencies between single classes. Without this feature, we would never be able to model parent‑child relationship as properly and effectivelly as we are now.

class Parent {

List<Child> children;

}

 

class Child {

Parent parent;

}

However, this is where tolerable support for cyclic dependencies ends. The acceptable boundary is clean: compilation unit.

4.3.1 Drawbacks

Please realize that you cannot actually compile one class without another. You need to have sources for both Parent and Child on source path and compile them as a single compilation unit. Compiler must do special effort to parse and resolve this cyclic dependency.

Ok, who cares?

Now imagine what this means for module‑aware compiler/build system. It means that you need to compile all modules bound together via cyclic dependency as a single compilation unit => as a single module. Then, compiler/build system must artificially split the resulting output (classes) into separate folders and/or jar/jam files in order to make this monolitic crap look like a modularized architecture (which it is NOT, actually; it is just single source tree packaged in multiple jar files which are effectivelly usable only as a whole).

Still don’t get it?

No suppose you have versioned modules. There is no clean way to introduce backward incompatible change to a single module in a cycle. You cannot update single module without actually rebuilding the others in a cyclic dependency. You would need to rebuild and update version number for all modules in a cycle. This is required because of how the cycle is built (single compilation unit).

Oh, hell, yes, I know, the real hacker will still find the way, but that’s not the point, is it?

Point is that cyclic dependencies between modules actually point out some serious flaws in the model/architecture. And this should never be encouraged, nor tolerated, nor supported by the module system, ever.

4.3.2 Summary

Cyclic dependencies between modules are evil because

1)        They support and encourage bad practice, bad models, and bad spaghetti architectures.

2)       Building such modules becomes more complex and error prone.

3)       Dependency management/resolution for module cycle becomes less transparent, more complicated and less robust.

4)       They just are (or will become) pain in the ass, sooner or later, and will hunt you down when you expect it the least.

The whole approach to versioning seems like we’re missing something needed to deeply understand the problem we need to solve.

The whole thing seems to be bacwards, it is just wrong.

It is not the client (user, importer) who can make any reliable decision about the version selection. Nor can he specify any hints beyond exact single version number he is aware of (simply because he is fixed in time and space). The only relevant imperative source for any such decision is the version history.

While I do agree with versioning scheme standardization, I think that with respect to module system, only following two things are important:

Ÿ           What is the developer trying to say by specifying the version constraint?

Ÿ           How can module system understand and satisfy this?

Version Requirement

In general, I see only two scenarios:

Ÿ           Static: I am conservative and I choose exact version of the module. I do not want any automatic updates.

Ÿ           Dynamic: I choose module version based on what contract it fulfills. I expect any future updates that are compatible (fulfill the same contract), to be aplied automatically (no matter what version number it is).

For ‘static’ scenario, there is no real job to be done by module system. Provide the required version or fail.

For ‘dynamic’ scenario, module system must

Ÿ           Select list of versions that are compatible with the required version

Ÿ           Negotiate the list with other modules requiring the same dependency

Ÿ           Pick up the most recent version from resulting list

Now how does the module system select the compatible versions?

Version History

The answer is in version history. Imported module must specify two things:

Ÿ           Its version history (whole path to the root, up to the version 1.0 or whatever the initial version was).

Ÿ           Oldest version number (from the history) this new version is compatible with.

This simple information is

Ÿ           enough to make any version comparisions with no extra effort

Ÿ           enough to support any versioning schemes, even the plain code names

Ÿ           enough to make the most reliable compatibility decision possible

This scheme is the best because it does not rely on client (importer) assumption about the compatibility of any future versions of the imported module. Instead, it consults the only imperative source: the module version history itself.

5.1             Summary

Module author specifies

Ÿ           Module version

Ÿ           Version history (up to the initial release)

Ÿ           Oldest version this new version is compatible with (must be listed in version history)

Importer (client) specifies

Ÿ           Exact version number of the module. (Missing version means newest, most recent version available)

Ÿ           Classifier that says whether this import statement is static or dynamic. (Default would be dynamic)

I am really very optimistic about this JSR and I hope and believe it will radically change the way we write, build and run software.

However, the long term goal we want to achieve must not be lost from the radar nor can it be distorted by the pseudo‑requirements for quick‑and‑dirty “solutions” wanted by many ‘fast‑food” developers.

The java module system should not be meant to allow people to do quick and dirty hacks. For this purpose, features like “java –classpath lib/*.jar” are provided, and they have their reasons (and right to live).

 


Bookmark blog post: del.icio.us del.icio.us Digg Digg DZone DZone Furl Furl Reddit Reddit
Comments
Comments are listed in date ascending order (oldest first) | Post Comment

  • "The Impl class is not accessible because it was hidden (not exported) by the module developer who thought it was a good idea to expose only API classes."

    This is the way it should be. If the author wants the implementation to be extensible, they should design and document for it. If they didn't do that much, there is no reason to expect that extension will work. Extensions made to classes that weren't designed to be extended are very fragile.

    Posted by: coxcu on October 25, 2006 at 08:59 AM

  • I understand what you are trying to say but for this particular purpose final keyword was introduced, there is no need to invent new complex and verbose constructs to do the same thing.

    I know my argument is a bit over-simplification right now, but the point I was trying to make in my review is meant to be elsewhere: module system should be built on top of modules, not module fragments

    I hope that's clear.

    Posted by: patrikbeno on October 25, 2006 at 09:07 AM


  • On the one hand, it bothers me that this, essentially, dumps CLASSPATH. But I only say that because of all the time and effort it has taken over the years to fundamentally understand CLASSPATH, ClassLoader, and their interactions and assumptions. To me, understanding Class Loading is a FUNDAMENTAL, but complex part of understanding Java. And, unfortunately, it's been my experience that many people simply do no understand it.


    If the JSR simplifies this process, then all the better. But it's not clear to me that it is now. Mind, I'm not defending CLASSPATH, it has all sorts of problems. But I do not look forward to having to switch mindsets between Java implementations and know whether Version X is using a CLASSPATH vs Version Y using the JSR, and juggling the complexities and nuances between the two.


    If nothing else, I enjoy the "purity" of WARs and EARs to, somewhat, simplfy the problem by (mostly) proving "one stop shopping" for what classes are where, and how they're loaded.

    Either way, this JSR will cause turmoil.

    Posted by: whartung on October 25, 2006 at 10:31 AM

  • Don't worry, I don't think JSR277 makes classpath go away. For reasons you have mentioned, CLASSPATH is here to stay. Probably emulated by the native module system.

    Class loading is not complex at all. We made it complex because we used the wrong approach:

    We model software as component and layers with well defined dependencies (between individual component and layers). Classpath did provide partial solution for layering but left us out in the cold when it comes to components. We did not have any other mechanism but layered classloading scheme and we tried to mix and match our component model into the layered class loaders. This was a mistake.

    Please note that this impedance mismatch (the need for flattening the natural component model) is the reason why class loading seems such a mysterious black box. However, component runtime may provide perfect match for your component model. people will simply naturaly understand how things are bound together without the need to be class loader experts... We all already do configure modules and dependencies in our IDEs... There will be no need for switching mindsets, you just realize that you don't need to flatten your model into the CLASSPATH to make it deployable... you just deploy and run it it "AS IS"

    Last but not the least, I must insist that there is no "purity" in WARs or EARs, especially EARs with WARs... This model is just fatal, I have my stories...

    Posted by: patrikbeno on October 25, 2006 at 11:04 AM

  • I just wanted to make one comment on this point: The people behind this JSR should address all issues outlined above and not release a half-baked solution. All too often in the past Sun has released half-baked solutions (see Generics for example) and it exploded in people's face.

    So I'm going to say something funny which I believe is true: edge cases matter, a lot! Please make sure you address all edge cases before considering a release.

    Gili

    Posted by: cowwoc on October 25, 2006 at 11:34 AM

  • thanks for the write up, patrick.

    I'm puzzled that adding support for a bunch of metadata to JARs in Java 7 requires rewriting class loading, changing the language, the VMs, the compilers, and all that. The spec is quite ambitious, judging by this writeup. Maybe that's a good thing. Maybe not. :)

    The whole concept of manual versioning looks pretty heavyweight to me, if it is not actually preventing class loading incompatibilities. A potentially better way to do it would be to record dependencies wrt an ABI, as linking constraints. If you do that at the granularity of classes, you can express a module as a union of the set of linking constraints that have to be resolvable for the loading of a module to succeed. Then your classical problem of version skew in metadata goes away, and you don't have to keep a copy of every version of a module around any more, only those where the ABI changes.

    J2SE APIs are full of cycles, so I can see why they had to handle them, as they want to be able to split the rt.jar into pieces. Been there, done that with Kaffe's class library some time ago.

    The rest ... oh well, the complexity price to pay is quite high for the feature. I'm looking forward with mild amusement to the extensions to the serialization and reflection specs having to deal with modules at runtime will trigger.

    That dynamic import policies thing is very odd. What's the rationale given for it?

    Am I guessing this right, that whole concept is based on binaries, and does not support including the source code in an artifact to be able to be rebuilt from the metadata alone?

    And if I got it right, if I change a class in an incompatible way, rebuild, and the build passes, the versioning information does not change automatically to indicate that? So I could have the same module with different, incompatible contents, but same version number, if I don't do the bookkeeping work manually, and the system would break apart, right?

    Posted by: robilad on October 25, 2006 at 12:39 PM

  • Patrik, just for your information, there has been another review of JSR-277 that might interest you:

    http://www.osgi.org/blog/2006/10/jsr-277-review.html

    Posted by: marrs on October 25, 2006 at 03:20 PM

  • marrs, thank you, I have noticed / read that :-) Pretty interesting...

    Posted by: patrikbeno on October 25, 2006 at 05:07 PM

  • robilad, there's much more to do that adding metadata into JARs. Redesigning class loading model is the primary goal (and is inevitable), other stuff is IMHO pretty much optional...

    the spec is quite ambitious, but not really ambitious enough... there is just too much issues left open...

    Ad. manual versioning: If this is comment on my versioning proposal, I must say:

    the goal of versioning is not to avoid version class incompatibility. its goal is to help module system make best decision when it tries to select the most suitable version. the model is still cooperative (cannot be different): if metadata is broken, decision made will be wrong either way...
    versioning can always be manual or automatic, it depends on your build system...
    my proposal is about version dependency constraints: I suggest switching the responsibility to select suitable version from user (importer) to module (exporter/provider).
    you don't have to keep copy of every version in repository either way. it has nothing to do with it... keeping version history log (numbers) in the last (most recent) version is enough to do job...


    Yes, JDK is full of cycles, hence unmodularizable... But that's not the reason to cripple module system with cyclic dependencies... If you cannot modularize JDK for whatever reason, let rt.jar be a songle module (I don't like it but it's better that cyclic deps in in the spec)

    Dynamic imports are reasonable backdoor to the declarative shortcut. You may always find a use case for this: e.g. parameterized runtime (select the module based on a system property, best example may be dynamic selection of the APi implementation - JDBC driver, for example).

    Automatic version updates are the job of a build system (compiler), not this JSR. If you rebuild and redeploy the module, you should update version number (manually or automatically). Exception to this rule is special type of build known to maven users as SNAPSHOT.
    SNAPSHOTS use build ID as a differentiator, however, it is not part of the version number. I.e. you should not need to asign unique version number to your development module (which you rebuild and redeploy 8 times a day)... But that's another story...

    Posted by: patrikbeno on October 25, 2006 at 05:32 PM

  • I totally agree with you!!I hope that all of your points make them way into the final draft. Specially the 4.2

    Posted by: imjames on October 25, 2006 at 11:52 PM

  • patrick, you may want to check out http://www.disi.unige.it/person/AnconaD/papers/Components_abstracts.html#ALZ0906


    We define a framework of components based on Java-like languages, where components are binary mixin modules. Basic components can be obtained from a collection of classes by compiling such classes in isolation; for allowing that, requirements in the form of type constraints are associated with each class. Requirements are specified by the user who, however, is assisted by the compiler which can generate missing constraints essential to guarantee type safety. Basic components can be composed together by using a set of expressive typed operators; thanks to soundness results, such a composition is always type safe. The framework is designed as a separate layer which can be instantiated on top of any Java-like language; to show the effectiveness of the approach, an instantiation on a small Java subset is provided, together with a prototype implementation. Besides safety, the approach achieves great flexibility in reusing components for two reasons: (1) type constraints generated for a single component exactly capture all possible contexts where it can be safely used; (2) composition of components is not limited to conventional linking, but is achieved by means of a set of powerful operators typical of mixin modules.


    which explains what I'm thinking about better than I could do in this comment box. :)

    I see manual versioning as just another piece of metadata attached to an artifact, that can be used to decide when multiple versions satisfy the type constraints. To the VM, the most important part is if the linking constraints on types can be successfully resolved.
    If I, say, want "ASM version 2 or later", and the ABI of ASM has changed in version 3, I want the module system to be able to figure it out all by itself, and give me a module staisfying the generated constraint "ASM version 2 or later and younger than version 3". Afaict from your write up, that's not possible with 277, and that's a bad thing, as it means that metadata needs to be continuously kept up to date, to keep up with updated dependencies breaking backwards compatibility. See http://article.gmane.org/gmane.linux.jpackage.general/10492 for an explanation why a good module system should address that. JPackage and other distributors do it through conventions, like insisting on buildability from source and automated QA work, though 277 could, if done right, solve that in an easier way.

    In a module system, a desireable property would be able to determine if the deployment of modules will work prior to actual deplyment, just by looking at the metadata, similarly how packaging systems work on software distributions. If the dependency resolution mechanism is turing-complete, though, i.e. 'you can write your own Java class here to resolve modules', we can not do that. At best, that allows yet another layer of dynamic behaviour, at worst it facilitates the creation of non-reproducible setups, and leads to the 'use this arbitrary bundled JAR snapshot because nothing else works and noone knows why as we don't have the code for it' effect that plagued projects using Maven last time I looked at it. I don't want the module I load to run arbitrary code to try to guess which JDBC driver to use, I want it to use the one I configured to be used, as I know the environment I'm deplying to (i.e. my constraints on deployment) much better than the person who wrote the module.

    From my experience of dealing with clever build systems, whenever developers try to do something they thought to be awfully clever, they end up with something that only really works well on the platform they tested with, and breaks apart badly in other places. JSR 277 does not need to be awfully clever, it just needs to solve the boring, simple problems of repositories, versioning and metadata in a simple, easy to use way, with lot of straightforward benefits, for a start.

    We can always add the clever stuff later, when we understand how the solution provided by the first incarnation of 277 is being used. But I'm not sure if 277 has done the analysis to figure out why neither OSGi, nor Maven, nor other approaches to Java modularity that have been heavily marketed in the past, have managed to suceed in solving the modularity problem conclusively in the Java space.

    Posted by: robilad on October 26, 2006 at 06:32 AM

  • I think you and the JSR EG are both missing the point in module versioning constraints. The information should be provided by BOTH parts :
    - application must express "i'm compatible with versions X to Y" meaning that it will run against module compatible with any of thoses versions, Y beeing the last version it is knowing of, and X the minimum requirement for it to run. It may exploit parts of Y that were not avalable in X or are not compatible with the same feature in X but for what it can use any of the two versions. For exemple I have many classes that exploit new Java 1.4 or 1.5 functions if they are avalaible for performance reason, but fallback nicely if only Java 1.3 is available.
    - module must express "i'm version V and providing service backward compatible down to version W". For Java 6 core it would say it is backward compatible with Java 1.0 core API.
    - The module system should select highest version that will make overlap the two sets X-Y / W-V. For exemple if application is compatible with version 1.2 to 5.0 and modules are avaliable in the repository that are respectively compatible with versions 1.0 to 4.0, 3.0 to 6.0, and 5.1 to 7.0, it will select version 6.0 because it is the highest version avalaible that meets compatiblity requirement of the application (that is a service compatible with version 5.0)
    The JSR mecanism would have selected module v4.0 because it is beetween 1.2 and 5.0 (or if application constraint had been 1.2+ it would have selected 7.0 that is not compatible !), and your proposal would have choosen the same because 1.2 it is the version of the module i'm compiling against and no other module is backward compatible with it. It will work but it's not optimum.
    Besides this overlapping scheme may be extended to transitive dependencies more easily (just intersect other dependence sets), leading in the highest most compatible version across all modules dependences beeing selected, reducing the risk of loading multiple versions.

    I agree that versionning history is a good way to provide a more generic scheme of versioning, but it will be way too boring to add all this stuff, so the default JSR versionning should be used at first, and history should be provided only for the qualifier part.

    Posted by: efayol on October 26, 2006 at 07:50 AM

  • Sorry for forgotting presentation tags in my previous post !
    I would just had another point about legacy JAR compatibility


    I think legacy JARs with version attributes in their manifest declaration should be seen as modules of their own without having to bundle them inside a .jam file MODULE-INF/lib directory, transforming them into module by adding a METADATA.module file, or even adding them explicitely in a repository.The default Boostrap repositories could handle this at launch from the standard classpath and extension directories.The "legacy module" could then be accessed the old way via the classpath (as always) or through the new module system, allowing new application to use transparently if no other module version is avalaible without it conflicting if there is a newer module (either legacy or jam).


    Besides, versioning in both manifest and module metadata is redundancy, and have no rationale. It will allow different version to be displayed either you load your simple JAM as a module or as a jar. This would lead to incompatibilities with applications that launch themself using the old way.

    Posted by: efayol on October 26, 2006 at 08:19 AM

  • To: robilad

    Thanks for the link, I will look at it... For now, I can comment the paragraph you cited:

    Compatibility problem is two-fold: binary and contractual.

    1) Unless we have a feature-rich design-by-contract facility at our disposal and supported by module system (component runtime), there is no way how runtime could possibly do something more than just binary validation. Problem is that you can break the backward compatibility without affecting binary compatibility (the contract change)

    2) Also, with the heavy use of reflection, runtime cannot succesfully verify all codebase and hence it cannot make any reliable decision.

    3) Further, automated constraint checking would require full scan of the whole JAR/JAM which causes awful slowdown.

    4) VM linking constraints: these are validated by the JVM, of course, and VM does this both eagerly (static class model) and lazily (e.g. method bodies), which the the right approach, I think. Trying to do all this in eager mode is both costly and unreliable (e.g. there can be a constraint which is valid at runtime but not at load time, like when you load a class via custom class loader)

    Dependency resolution mechanism: I see no difference between declarative and programatic dependencies nor do I see how this affects reliability of the system. Maybe I should clarify: programatic setup of dependencies should be just like programatic creation of the MODULE.metadata. All further processing is unified for both models, i.e. module system validates metadata, consults repositories, makes dependency/conflict resolution, etc). This model may be a little more dynamic to be always reproducible but that complaint is in fact applicable to every dynamic system. Just get used to it, not everything needs to (or can) be static...

    One note to your "ASM version 2 or later and younger than version 3" example:
    I think you should never be in need to say that. All you have to say is "ASM version 2 or compatible" and let module system pick up the version that matches, be it 3.0 or 2.1-mybranch. And AGAIN, no module system can make any compatibility decision by itself (binary vs. contract compatibility)

    You write about adding clever stuff later but what you describe (module system being able to figure it out all by itself) would be a damn smart thing if it worked. I fear It never will just because you never fully automate dependency compatibility resolution, this just needs to be cooperative. You only have to decide who is responsible for what (module client vs. module author vs. module system).

    Ad Maven: This one is quite soffisticated build system, sure, sometimes being too soffisticated or too simple for a given problem), but when it comes to modularity or dependency management, Maven is still just a toy... I would not consider it a reference...

    Posted by: patrikbeno on October 27, 2006 at 03:11 AM

  • To: efayol

    I think you are just complicating this with no added value. And I fear you misunderstood many statements from both may review/proposal and the JSR itself...

    1) you should not need to say 'ranges', you should just say "I want version 1.3 or whatever that says it's compatible"

    2) you should not need to specify any "optimum" version because "minimum" should be enough. If there is anything better (optimum) available, system will provide it... That's why I reject version patterns like "1.3.x" which will insist on 1.3 version family only... I want to say "1.3 or compatible" and I might end up with 5.0 if it says it's compatible with 1.3... That's just fine, just enough....

    3) Repeating myself: Backward compatibility is determined by the module author. he knows the history and he knows what he changed. It cannot be decided by anyone else. If module version 5.0 says it is still compatible with version 1.0, so be it: any client that compiled against 1.0 may end up running against 5.0...

    4) dependency resolution is MEANT work primarily exactly like that: selecting highest compatible version across all modules. that's what dependency resolution IS. Other schemes (like loading multiple concurrent versions) are much more complicated (see section 4.1 in my review)

    5) version history may be boring but it is necessary and only way. Besides this, it's just incrementally adding new version number to the list with one extra change in case you are breaking the compatibility... the former can be done automatically by build system... so what's the deal?

    Posted by: patrikbeno on October 27, 2006 at 03:33 AM

  • The module author determines the INTENDED backward compatibility, but doesn't always achieve it due to bugs/regressions. So I think it is reasonable to say that you require "1.3 or compatible, except for 1.4.1".
    Just search for "regressions" on the bug parade for instances where such a restriction might be required.

    Posted by: mthornton on October 31, 2006 at 03:16 AM

  • mthorton: now that makes sense, thanks for pointing that out.
    I must say I was aware of this issue. However, we all should realize that this is not about how the system is supposed to work, it is not how its core semantics should be defined... Instead, it is about how you handle exceptions and unwanted/unexpected/undesired configurations.

    And I think that exceptions should be treated as exceptions, they should *not* be embraced as a natural first-class feature of the system/model. What you describe is a BUG. The same as missing (undeclared) dependency (and how is the module system supposed to handle that?).

    I mean, we need to make a distinction between
    (a) how the healthy well defined system works and how the *properly designed and implemented* pieces (modules) are linked together
    (b) and what kind of backdoors, shortcuts and workarounds and patterns we define to help as deal with inaccuracies, errors and mistakes we make while using the system.

    We should not mix these things together. JSR 277 EDR does exactly this kind of unwanted MIX: it ignores the fact that these are two separate issues.

    So, to make a conclusion:
    - client declares exact version of the dependency
    - additionally (and optionally), client may provide list of version excludes (this is where some of *simplifed* version pattern syntax may be useful); however, usually you should not need that.

    Now before you say we're back where we started, I must emphasize the crucial difference here (and think again if you don't see it)


    Posted by: patrikbeno on October 31, 2006 at 05:17 AM



Only logged in users may post comments. Login Here.


Powered by
Movable Type 3.01D
 Feed java.net RSS Feeds