-
Notifications
You must be signed in to change notification settings - Fork 1
[core] Upgrade the Semantic Versioning whitepaper: breaking change introduces new contract #2958
Comments
Comment author: @rinswind Bug BZ#3081 lead to a discussion about how to introduce breaking changes to an API so that they cause least downstream breakage (do not fork the universe), but allow gradual adoption even within one bundle. The discussion converted on the conclusion that in a way a package name is bound immutably to a meaning that can not be changed if the above properties are required. A breaking change introduces a brand new thing with a new meaning and therefore a new package name has to be bound to that new meaning. In short: breaking changes should be introduced with new packages, not by bumps to the major version of the existing packages. Logically every package stays forever at major version 1. The Semantic Versioning whitepaper should be upgraded to reflect this new thinking. A number of details need to be hashed out.
|
Comment author: @rinswind As an initial iteration I propose a convention one can use when starting from a green field:
The goal is not only to make it mechanically possible to have gradual migration, but also to communicate clearly
The trade-off is that everything being forever at "1.x.y" looks odd. |
Comment author: @tverbele I still disagree though (or I am missing something). The meaning can still remain the same although a version 2.0.0 seems more appropriate. For example org.example.foo; version=1.0.0 provides an API to do task Foo. Now I discover Promises and PushStreams, and decide to totally rewrite my API using Promise and PushStream return types. The new API breaks everything out there, but the meaning is still the same, it still does task Foo. To me it seems legit to have this new API as package org.example.foo; version=2.0.0. Why is it better to find out a new name, although the meaning stays the same? Why is it better to name the package org.example.foo.v2? |
Comment author: @rinswind (In reply to Tim Verbelen from comment BZ#2)
Then we must refine the terms. Perhaps just "breaking contract" - in the sense we use it in OSGi. This includes binary compatibility and behavioral compatibility. The goal is to provide a smooth migration path to the new contract. |
Comment author: @tverbele Ok I see, you want a bundle to be able to import both v1.0.0 and v2.0.0 hence v2.0.0 should be a different package name. Still I find a version inside the package name like org.example.foo.v2 a bit silly and would not make that the recommended naming convention. Also, I wonder whether is this an often required use case. I mean, if my bundle contains org.example.impl.foo; version=1.0.0 which implements org.example.foo; version=1.0.0, and then org.example.foo; version=2.0.0 is released, then I'd have to totally rewrite my bundle anyway. Why not just release org.example.impl.foo; version=2.0.0 that implements the new API? People wanting the old API can still use my v1.0.0 bundle? |
Comment author: @rinswind (In reply to Tim Verbelen from comment BZ#4)
I think because in practice someone downstream can break with a uses constraints violation out of which there is no way out unless an entire transitive dependency chain migrates. If we come to disagree this is a good idea - so be it. It's still valuable to have the argumentation against that. |
Comment author: @rinswind Here is a further refinement of the proposition of this bug: A better term than "meaning" for OSGi is "contract". We version contracts, not artifacts. Sometimes a contract has only one provider, sometimes a contract ships with that provider. The rest of the world tends oversimplify versions to only this last case. As we know there are two parties collaborating around the contract:
The goal of the contract is fulfilled when it is used. This is why the consumers are more important than the providers. This is reflected in the semantic version, where the most significant number (the major version) describes compatibility for the consumers. A critical trait of the contract is that it allows consumers and providers to evolve at different pace:
The proposition of this bug is that a major version change should be considered a new contract, rather than an evolution of the old one. This then will allow both consumers and providers to migrate from the old contract to the new gradually, rather than to split the universe. Splitting the universe is bad because it means the new contract can fulfill it's goal only after everyone migrates in reverse dependency order. I.e. the trait of a contract to allow independent evolution is broken - consumers and providers become locked. The consumers are not in a position to compensate by consuming both contracts, nor are providers by implementing both contracts. Another way to look at it is that a major version change is equivalent to the state when the contract was first introduced and there were no providers. Again the contract has to be adopted in reverse dependency order. So it looks more like the major version change is a new contract for all practical purposes. |
Original bug ID: BZ#3090
From: @rinswind
Reported version: R7
The text was updated successfully, but these errors were encountered: