Yesterday Mark Reinhold posted a link to the new requirements on Jigsaw. He states a more ambitious goal to make Jigsaw not just for VM developers only as they said before. The good news is that OSGi is recognized. The even better news is that now there are requirements it is obviously clear that OSGi meets virtually all of them, and then some. The bad news is that despite OSGi meeting the requirements it does not look likely that Jigsaw will go away anytime soon.
As this round will likely get me another 15 minutes of fame, let me use this fame to try to explain why I consider the Jigsaw dependency model harmful to the Java eco-system.
Jigsaw's dependency model must be based on Maven concepts according to requirement #1. This model is the same as Require-Bundle in OSGi. It is based on a module requiring other modules, where modules are deployment artifacts like JARs. To deploy, you transitively traverse all their dependencies and put them in the module system to run. That is, a module for Jigsaw is a deployment module, not a language module.
It is a popular solution because it is so easy to understand. However, Einstein already warned us: "A solution should be as simple as possible, but not simpler." The problem of managing today's code base for large applications is complex and all of us in the Java eco-system will be harmed with a simplistic solution.
The key problem is that if you depend on one part of a JAR you inherit all its transitive dependencies. For example, take the JAR Spring Aspects JAR. Even if my code only uses the org.springframework.beans.factory.aspectj package in this JAR, the Spring Aspects JAR will unnecessarily provide me with on dao, orm, jpa, as well as the transaction package. If I deploy my application I am forced to drag in the dependencies of these packages, which drag in other JARs, which each have additional dependencies, etc. etc. During deployment tens (if not hundreds) of JARs are dragged in even though those packages can never be reached from my code. Not only is this wasteful in memory, time, and complexity, it also inevitably causes problems in compatibility. For example, if the jpa dependency and the orm dependency transitively depend on different versions of the log4j JAR I cannot even run my code for no real reason whatsoever. In practice this problem is often ignored, for example maven will take the first log4j version it encounters, regardless if this is a suitable version. The value of type safety is questionable if that kind of fudging is acceptable.
Maven users are already all too familiar with this problem: many are complaining that maven downloads the Internet. This is not bashing maven, maven only does what it is told to do in the poms. In Tim O'Brien's blog he explains that Maven does not download the Internet, People download the Internet. Though he rightly blames the poms, he ignores the fact that the dependency model makes it hard to work differently. It is like you want a candy bar but you can only get the whole supermarket. He advises each JAR to be self contained and completely modularized to not drag in extraneous dependencies. Agree. Unfortunately this means you get lots and lots of JARs. And with lots of JARs on your module path without side by side versioning the compatibility issues like the above log4j example explode. And Jigsaw is not likely to have simultaneous multiple versions in run-time when critically reading the requirement.
In Unix dependency managers the transitive aspect is not such a big deal because dependencies are relatively coarse grained and there is a reason why you usually have to build stuff to make it compatible with your platform. Java is (thank God) different. Java dependencies are not only much more fine grained, they are binary portable to any platform, most important of all, often they have no dependency on an implementation but use a dependency on a contract instead. Unix was brilliant 40 years ago and is still impressive today but let's not undervalue what Java brought us. We must understand the differences before we revert to a 40 year old technology from an operating system to solve the problems of a modern and unique programming environment like Java.
The key advantage that Java brings to the dependency landscape is that each class file encodes all its dependencies. Give me your class file and I will tell you what packages you depend on. For example, when I compile my JAX-RS class against the jersey JAR(s) you will find that the class file only depends on the javax.ws.rs package even though the Jersey file contains lots of implementation packages. However, in the Jigsaw world I must select a module JAR that provides that package. Best case is to pick the JSR 311 API JAR for compilation to ensure I do not accidentally pick up implementation classes. However, I do not want to make the life of the deployer hard by unnecessarily forcing a specific implementation in run-time. So what module should I pick to depend on?
Despite common believe, we do not need fidelity across all phases between the build time and run-time; we need to compile against API and run against implementations that are compatible with that API. A good Java module system must allow me to depend on an API module at compile time and select an appropriate implementation at deployment-time. Forcing the compile time dependency to be the same as the run-time dependency creates brittle systems, believe me. The problem can be solved by separating the concept of a deployment module (the JAR) and the language module.
We could define the concept of a language module in Java and allow the JAR to contain a number of these modules. Surprisingly (for some), Java already provides such a language module, they are called packages. Packages provide a unique name space, privacy, and grouping, all hallmarks of modularity. The JLS introduction even names them as similar to Modula's modules. Actually, module dependencies on packages are already encoded in the class files. The only thing we need to add to the existing system is versioning information on the package and record this version in the class file. During deploy-time a resolver can take a set of JAR's (which can be complete) and calculate what modules should be added depending on the actual environment. E.g. if it runs on a Mac it might deploy another set of additional JAR's then if it runs on the PC or Linux.
To manage real world dependencies we need this indirection to keep this manageable. From 2010 to 2017 the amount of software will double. If we want to have a fighting chance to keep up we must decouple compile time from deployment time dependencies and have a module system that can handle it.
It seems so obvious to build on existing concept in the Java language that it begs an explanation why the transitive dependency model on JARs is so popular. The only reason I can think of is that is easier for the module system providers to program. In the Jigsaw model traversing the dependencies is as easy as taking the module name + version, combining it with a repository URL, doing some string juggling and using the result as a URL to your module. Voila! Works with any web server or file system!
Package dependencies require an indirection that uses a mapping from package name to deployment artifact. The Jigsaw/Maven model is like having no phone number portability because the operators found a lookup too difficult. Or worse, it is like having a world without Google ...
So for the convenience of a handful of module system providers (in the case of Jigsaw just one because it is not a specification) we force the rest of us to suffer? Though people have learned to use dynamic class loading and not telling the truth in their poms to work around dependency problems inherent in the dependency model we should not encode this in my favorite language. Should we not try to address this problem when a new module system is started? Or better, use an already mature solution that does not suffer the impedance mismatch between compile-time and run-time?
At JavaOne 2007 Gilad Bracha, then Sun's man in charge of JSR 277, told us he had started JSR 294 because he did not trust the deployers to handle the language issues. At that particular time this remark puzzled me.
Peter Kriens
P.S. Opinions stated in this blog are mine alone and do not have to agree with the OSGi Alliance's position
Thursday, May 26, 2011
Tuesday, May 24, 2011
What If OSGi Ran Your Favorite Language?
My bookshelf is full of books about programming languages, from APL to Z. They are a little bit dusty because the last decade OSGi kept me confined to Java. However, I think it is time we should broaden our scope. There is just more life out there then just Java (really!).
If you look at the OSGi API then you see that the class loader modularity is strongly tied to Java. I actually do not know any other language that provides a way to intercept class loading in the flexible way that Java allows this to be done. It is also clear that most other languages out there are not type safe, something the OSGi has been fanatic to maintain and support.
However, the most important part of the OSGi programming model has always been the µservices, an Inter Module Communication (IMC) mechanism, that has virtually no overhead but is a surprisingly powerful design primitive; µservices encode so many important design principles.
OSGi µServices are also the cornerstone of Distributed OSGi. As so much code is not written in Java, how could we provide the service model to other languages? Obviously JVM languages can usually use the Java API but that is likely not optimal for a language, you like to take advantage of the unique possibilities each language provides.
Now, I could sit down and dust off my Smalltalk and write a service registry with the OSGi semantics for Smalltalk. With a bit of effort I could probably do it for C(++), maybe even Basic? However, it is clear that most language experts do not live in my house. So the question to you is, how would you make the µservice model look in another language while remaining compatible to the OSGi semantics?
Write a blog or google document how your µservice API would look in your favorite language and place a comment on this blog with a link. I am really curious how those APIs would look like. Give me your Python, Ruby, Haskell, Scheme, Clojure, Smalltalk, Javascript, ActionScript, Scala, Cobol, Perl, PHP, .NET, Beta, ML, Modula, Pascal, Fortran, Forth, Erlang, Postscript, Bash, and any of those other languages you like!
Peter Kriens
P.S. This is something I am highly interested in which does not necessarily imply that the OSGi Alliance is interested in pursuing this.
If you look at the OSGi API then you see that the class loader modularity is strongly tied to Java. I actually do not know any other language that provides a way to intercept class loading in the flexible way that Java allows this to be done. It is also clear that most other languages out there are not type safe, something the OSGi has been fanatic to maintain and support.
However, the most important part of the OSGi programming model has always been the µservices, an Inter Module Communication (IMC) mechanism, that has virtually no overhead but is a surprisingly powerful design primitive; µservices encode so many important design principles.
OSGi µServices are also the cornerstone of Distributed OSGi. As so much code is not written in Java, how could we provide the service model to other languages? Obviously JVM languages can usually use the Java API but that is likely not optimal for a language, you like to take advantage of the unique possibilities each language provides.
Now, I could sit down and dust off my Smalltalk and write a service registry with the OSGi semantics for Smalltalk. With a bit of effort I could probably do it for C(++), maybe even Basic? However, it is clear that most language experts do not live in my house. So the question to you is, how would you make the µservice model look in another language while remaining compatible to the OSGi semantics?
Write a blog or google document how your µservice API would look in your favorite language and place a comment on this blog with a link. I am really curious how those APIs would look like. Give me your Python, Ruby, Haskell, Scheme, Clojure, Smalltalk, Javascript, ActionScript, Scala, Cobol, Perl, PHP, .NET, Beta, ML, Modula, Pascal, Fortran, Forth, Erlang, Postscript, Bash, and any of those other languages you like!
Peter Kriens
P.S. This is something I am highly interested in which does not necessarily imply that the OSGi Alliance is interested in pursuing this.
Friday, May 20, 2011
What You Should Know about Class Loaders
The more (open) source code I see the more I realize that so many developers do not understand the implications of class loaders and just try different things until it seems to work. Not that the Class Loader API is that hard but the implications of Class loaders are often not understood. In a modular environment, class loader code wreaks havoc.
Unfortunately, Class Loaders have become popular as the Java extension mechanism. Developers love the idea that you can take a String and turn it into a Class, there are so many hacks you can do with this! However, what is often not realized that in a modular world this just cannot work. The implicit assumption is that each Class object can be identified with a unique name. It should not take much more than 2 seconds to realize that in a modular world we also require the name of the module because the Class name is no longer unique. That is, class unique.X can actually occur in multiple modules. So given the string "unique.X" there is no way to identify from which module to load this class. In a modular world there are multiple class name spaces!
What is the advantage of having multiple class name spaces? Well, it solves a huge problem in larger applications that are built from many parts that come from different sources. Think open source. Take an example of logging. Logging is so popular that everybody does it, unfortunately with different libraries. In practice, it becomes hard to ensure that all your dependencies use the same logging library. In a modular world you can connect each library to its correct logging module. In a non modular world, the first one wins because libraries are placed on a linear search path; you often have no clue who won until you get an error. Obviously this problem is not limited to logging. It is actually kind of flabbergasting that a weak mechanism like the class path is acceptable for professional applications.
So why are developers using strings so often to get a class? Well, the core reason is that Java does not have an extension mechanism. No, the Service Loader is not an extension mechanism, it is encoding of a bad convention leaking its implementation out of all its methods. Extensions through class loading are highly popular, very often in Factory patterns. Sadly, extensions are exactly the place where you want to cross module boundaries. What is the use of an extension that happens to be in your own module? So these strings often contain the name of an implementation class that implements a collaboration interface. The three most important rules of modularity are: Hide, Hide, and Hide. Do you really want to expose the implementation class from a module? Isn't that the antithesis of modularity?
In spite of these problems, the pattern to use class loaders for extension mechanisms has become highly popular. Today we even have generations that actually do not realize how bad it is to use global variables (class names) to access context through static variables. My programming teacher in 1979 would have set me back a class when I would have proposed such a solution. However, as you know, you go to war with the army you have, not the army you might want or wish to have at a later time. So the class loading hack has become highly popular in lieu of an alternative. So popular that it is used in places where it is not even needed. Worse, it will bite you whenever you move your code base to a modular world.
So here are number of rules/hints you can use to prepare your software for a modular world (and simplify your life at the same time).
Peter Kriens
Unfortunately, Class Loaders have become popular as the Java extension mechanism. Developers love the idea that you can take a String and turn it into a Class, there are so many hacks you can do with this! However, what is often not realized that in a modular world this just cannot work. The implicit assumption is that each Class object can be identified with a unique name. It should not take much more than 2 seconds to realize that in a modular world we also require the name of the module because the Class name is no longer unique. That is, class unique.X can actually occur in multiple modules. So given the string "unique.X" there is no way to identify from which module to load this class. In a modular world there are multiple class name spaces!
What is the advantage of having multiple class name spaces? Well, it solves a huge problem in larger applications that are built from many parts that come from different sources. Think open source. Take an example of logging. Logging is so popular that everybody does it, unfortunately with different libraries. In practice, it becomes hard to ensure that all your dependencies use the same logging library. In a modular world you can connect each library to its correct logging module. In a non modular world, the first one wins because libraries are placed on a linear search path; you often have no clue who won until you get an error. Obviously this problem is not limited to logging. It is actually kind of flabbergasting that a weak mechanism like the class path is acceptable for professional applications.
So why are developers using strings so often to get a class? Well, the core reason is that Java does not have an extension mechanism. No, the Service Loader is not an extension mechanism, it is encoding of a bad convention leaking its implementation out of all its methods. Extensions through class loading are highly popular, very often in Factory patterns. Sadly, extensions are exactly the place where you want to cross module boundaries. What is the use of an extension that happens to be in your own module? So these strings often contain the name of an implementation class that implements a collaboration interface. The three most important rules of modularity are: Hide, Hide, and Hide. Do you really want to expose the implementation class from a module? Isn't that the antithesis of modularity?
In spite of these problems, the pattern to use class loaders for extension mechanisms has become highly popular. Today we even have generations that actually do not realize how bad it is to use global variables (class names) to access context through static variables. My programming teacher in 1979 would have set me back a class when I would have proposed such a solution. However, as you know, you go to war with the army you have, not the army you might want or wish to have at a later time. So the class loading hack has become highly popular in lieu of an alternative. So popular that it is used in places where it is not even needed. Worse, it will bite you whenever you move your code base to a modular world.
So here are number of rules/hints you can use to prepare your software for a modular world (and simplify your life at the same time).
- Do not use class loaders - If you feel the urge to use a class loader think again. And again. Class loaders are highly complex beasts and your view of the world might not match the world your code is actually used in.
- Do not use class loaders yet - Only for the experts.
- Extensions - If you have to connect to other modules use a Inter Module Communication (IMC) mechanism. A good IMC provides access to instances and not classes. If you do not have one, just make one yourself it is that not much code. Even better, use the OSGi service registry. Best is a real OSGi framework but if you're not yet ready for that step use something like PojoSR from Karl Pauls.
- Class.forName is Evil - Class.forName will pin classes in memory forever (almost, but long enough to cause problems). If you're forced to do dynamic class loading use ClassLoader.loadClass instead. All variations of the Class.forName suffer from the same problem. See BJ Hargrave's blog about this.
- Understand the Context - I've just debugged GWT code that was unnecessarily mucking around with class loaders when it needs to deserialize the request. The first token was a GWT class but the remainder of the request was the interface and parameters related to the delegate (the object implementing the RPC method.) However, for some reason they used the Thread Context Class Loader (you're in deep s**t if that one becomes necessary), and Class.forName. Completely unnecessary because they had access to the delegate object and thus its class loader. The class loader of this object must have access the RPC interface as well as all its parameters. There is a similar situation for Hibernate. You can name the classes in an XML file or you can give Hibernate the class objects for the entity classes. If you give it the class objects Hibernate works fine in a modular world. Give it XML and it chokes because in a modular world there is no 1:1 relation ship to class name and Class object. Reasoning about the context in which a class is loaded can prevent problems.
- Centralize - If you really need class loaders centralize and isolate your code in a different package. Make sure your class loading strategies are not visible in your application code. Module systems will provide an API that allows you to correctly load classes from the appropriate module. So when you move to a module system you only have to port one piece of code and not change your whole code base.
- Never assume that a class name identifies one class - The relation between class name and Class object is 1:n, not 1:1. You might desperately want it to be true but in a modular world this does not make it true.
- DynamicImport-Package and buddy class loading are not an alternative - If you browse the web you find lots of well meaning people pointing to these "solutions". Unfortunately, these "solutions" move you right back all the problems of the class path.
Peter Kriens
Tuesday, May 17, 2011
Start with Why (OSGi) ...
Someone pointed me at a TED talk from Simon Sinek that explains why it is so easy to forget to communicate the why when you try to evangelize a technology. Why have I been working hard these last 13 years, traveling a million miles, to develop and promote a technology like OSGi? Tirelessly explaining how OSGi works for sometimes skeptical mainstream developers?
The why lies buried in my youthful encounter with objects in the early eighties. Discovering objects was like an epiphany, suddenly a lot of the techniques I had found to work well were put in a framework and got names. The excitement of reading the 1981 Byte issue on Smalltalk was immense. Objects were exactly what I wanted for my software development! In that excited mode I convinced my manager that we should switch to objects as soon as possible because it would save us so much in the future. I painted a rosy picture in the sky about how software could be developed in the future of reusable classes. Developing an application would largely consist of picking the right classes, and voilá! I got the budget easily.
Objects did provide a significant benefit and 10-15 years after the Byte issue on Smalltalk they had therefore become mainstream. Though it is still easy to point out how out industry is struggling with complexity I do believe objects provided a significant value in allowing us to handle much more complex problems. However, the vision of assembling an application out of components never happened. What happened instead is that most large object oriented programs turn into a big ball of mud that is so entangled that it becomes impossible to find reusable components in the haystack.
Thirty years after the Byte issue on Smalltalk the world looks very different. Open source software has fundamentally changed the nature of software. Open source seems to have achieved the quest of reusable components because today these open source projects are reused on a grand scale.
However, when I struggle with integrating different software packages and their myriad of dependencies I am not so sure anymore. Badly documented, missing crucial metadata, extremely sensitive to their environment, complex configuration, incompatible versions, avalanches of logging, tons of XML, and mysterious crashes, then I know we're not there yet, it is still way too hard. There should be a better way! How can we better leverage open source projects so that these projects are easier to use together?
The model we envisioned in the eighties was plug an play, the way we can go to the department store and get drills, nuts, fasteners, and bolts from different vendors that work together because they use standards so they work together. This is the dream that kept me going for the past decade. OSGi is the only software standard that provides a substrate on which reusable components can flourish. Though OSGi has not reached the grail, it is arguably closer than anything else.
I am convinced that the next big step in software is the OSGi model: bundles collaborating through substitutable services. However, just like the mainstream procedural developers had to adjust their ways to take advantage of objects, today's mainstream developers have to adjust their software to take advantage of OSGi (and not run head first against the enforced module boundaries). Some developers reject OSGi because they deem it too complex but what is not too complex if you do not understand the why? Procedural developers also felt that objects were way too complex and wasteful. An investment in a technology is always required to get its benefits.
After thirty years the vision of reusable components has come tantalizing close with OSGi. However, to go that last mile we need your help to make OSGi the rock-solid foundation for the mainstream software developers.
Peter Kriens
P.S. As always, these are my personal opinions and are not necessarily aligned with the OSGi Alliance
The why lies buried in my youthful encounter with objects in the early eighties. Discovering objects was like an epiphany, suddenly a lot of the techniques I had found to work well were put in a framework and got names. The excitement of reading the 1981 Byte issue on Smalltalk was immense. Objects were exactly what I wanted for my software development! In that excited mode I convinced my manager that we should switch to objects as soon as possible because it would save us so much in the future. I painted a rosy picture in the sky about how software could be developed in the future of reusable classes. Developing an application would largely consist of picking the right classes, and voilá! I got the budget easily.
Objects did provide a significant benefit and 10-15 years after the Byte issue on Smalltalk they had therefore become mainstream. Though it is still easy to point out how out industry is struggling with complexity I do believe objects provided a significant value in allowing us to handle much more complex problems. However, the vision of assembling an application out of components never happened. What happened instead is that most large object oriented programs turn into a big ball of mud that is so entangled that it becomes impossible to find reusable components in the haystack.
Thirty years after the Byte issue on Smalltalk the world looks very different. Open source software has fundamentally changed the nature of software. Open source seems to have achieved the quest of reusable components because today these open source projects are reused on a grand scale.
However, when I struggle with integrating different software packages and their myriad of dependencies I am not so sure anymore. Badly documented, missing crucial metadata, extremely sensitive to their environment, complex configuration, incompatible versions, avalanches of logging, tons of XML, and mysterious crashes, then I know we're not there yet, it is still way too hard. There should be a better way! How can we better leverage open source projects so that these projects are easier to use together?
The model we envisioned in the eighties was plug an play, the way we can go to the department store and get drills, nuts, fasteners, and bolts from different vendors that work together because they use standards so they work together. This is the dream that kept me going for the past decade. OSGi is the only software standard that provides a substrate on which reusable components can flourish. Though OSGi has not reached the grail, it is arguably closer than anything else.
I am convinced that the next big step in software is the OSGi model: bundles collaborating through substitutable services. However, just like the mainstream procedural developers had to adjust their ways to take advantage of objects, today's mainstream developers have to adjust their software to take advantage of OSGi (and not run head first against the enforced module boundaries). Some developers reject OSGi because they deem it too complex but what is not too complex if you do not understand the why? Procedural developers also felt that objects were way too complex and wasteful. An investment in a technology is always required to get its benefits.
After thirty years the vision of reusable components has come tantalizing close with OSGi. However, to go that last mile we need your help to make OSGi the rock-solid foundation for the mainstream software developers.
Peter Kriens
P.S. As always, these are my personal opinions and are not necessarily aligned with the OSGi Alliance
Friday, May 6, 2011
Hudson at Eclipse, Versioning, and Semantics
Hudson is moving to Eclipse and that is great news, hope they will now seriously adopt the OSGi bundle as their plugin model and leverage the OSGi µservice model. Knowing that Stuart McCulloch is heavily involved makes me confident that this will work out fine.
One of the early discussions that popped up was a mail thread about versioning. Though the discussion is moving in the right direction (adopting the OSGi version model) the discussion was correct about the syntax but seem to assume that compatibility can be discussed bi-laterally. I am afraid that they are not alone, most people assume that there are only two parties involved with versioning (though in the case of marketing versions the assumption is just one party). However, the key insight we had in the OSGi was that it is a tri-lateral problem.
It is a tri-lateral problem because today interface based programming is not only very popular, it is a best practice. They key advantage of interface based programming is that the Provider and Consumer are bound to an API (the interface) but not to each other. That is, interface based programming removes implementation dependencies. However, though this model significantly reduces the coupling, it unfortunately complicates the versioning in a rather subtle way. So subtle that its implications are ill understood.
With interface based programming there are 3 independent parties:
So how do we version this? Versions are a mechanism to communicate the evolution path of an entity. If I have an entity X that depends on entity Y, how do I decide that Y is compatible with X's assumptions? If X is a Consumer, backward compatibility is relatively easy to provide. If X is a Provider, backward compatibility is much more restricted. We therefore need to encode the following cases in a version:
The only thing now left is the granularity of the version. Do we put the version on the JAR (bundle) or the interface class? If the JAR contained only API then the JAR would be a good granularity. However, in practice JARs are often not very cohesive and carry besides their API also implementation classes and convenience dependencies. That is, their fan out is uncomfortably large causing maven to download the internet. Classes are too small because most API's consist of a set of classes and versioning these separately is not sensible. The perfect granularity to depend on is therefore the package. A package is a cohesive set of classes that has therefore the right granularity for an API.
I hope that this blog clarifies why it is important to understand versioning in the light of modern programming.
Peter Kriens
One of the early discussions that popped up was a mail thread about versioning. Though the discussion is moving in the right direction (adopting the OSGi version model) the discussion was correct about the syntax but seem to assume that compatibility can be discussed bi-laterally. I am afraid that they are not alone, most people assume that there are only two parties involved with versioning (though in the case of marketing versions the assumption is just one party). However, the key insight we had in the OSGi was that it is a tri-lateral problem.
It is a tri-lateral problem because today interface based programming is not only very popular, it is a best practice. They key advantage of interface based programming is that the Provider and Consumer are bound to an API (the interface) but not to each other. That is, interface based programming removes implementation dependencies. However, though this model significantly reduces the coupling, it unfortunately complicates the versioning in a rather subtle way. So subtle that its implications are ill understood.
With interface based programming there are 3 independent parties:
- C - Consumer
- A - API
- P - Provider
So how do we version this? Versions are a mechanism to communicate the evolution path of an entity. If I have an entity X that depends on entity Y, how do I decide that Y is compatible with X's assumptions? If X is a Consumer, backward compatibility is relatively easy to provide. If X is a Provider, backward compatibility is much more restricted. We therefore need to encode the following cases in a version:
- Backward compatibility with all
- Backward compatibility with Consumers
- Backward compatibility with Providers
- micro - Changes in the micro part do not affect Consumers nor Providers
- minor - Changes in the minor part affect Providers but not Consumers
- major - Changes in the major part affect Providers and Consumers
The only thing now left is the granularity of the version. Do we put the version on the JAR (bundle) or the interface class? If the JAR contained only API then the JAR would be a good granularity. However, in practice JARs are often not very cohesive and carry besides their API also implementation classes and convenience dependencies. That is, their fan out is uncomfortably large causing maven to download the internet. Classes are too small because most API's consist of a set of classes and versioning these separately is not sensible. The perfect granularity to depend on is therefore the package. A package is a cohesive set of classes that has therefore the right granularity for an API.
I hope that this blog clarifies why it is important to understand versioning in the light of modern programming.
Peter Kriens