In a graph of interconnected software components how do we know if a node changes? Then, how do we know if the composite assembly will still function as intended given this change?
Versioning of the nodes addresses the first problem; semantic versioning of nodes addresses both problems.
Semantic versioning in OSGi is achieved in the following manner:
- A version has 4 parts:
major 1 minor 1.1 micro 1.1.1 qualifier 1.1.1.qualifier
- A version policy must is defined: i.e. an agreed semantic interpretation. A change in:
- major ➞ a breaking change
- minor ➞ a backward compatible changes
- micro ➞ a bug fix (no API change)
- qualifier ➞ a new build
- Export-Package (or Capabilities) - Here the decision was taken to support a single export version (i.e.
Export-Package: com.acme.foo; version=1.0.2). So a non-breaking to
com.acme.foomight be represented by a version change 1.0.2 ➞ 1.1.0; where as the subsequent version change 1.1.0 ➞ 2.0.0 represents a breaking change.
- Import-Package (or Requirements) now specified the range of acceptable Export-Packages (Capabilities) versions (i.e. Import-Package: com.acme.bar; version=”[1,2)”). Square brackets ‘[‘ and ‘]‘ are used to indicate inclusive and parentheses ‘(‘ and ‘)‘ to indicate exclusive. Hence a range [1.0.0, 2.0.0) means any Capability with version at or above 1.0.0 is acceptable up to, but not including 2.0.0.
In the this example we know that
com.acme.bar will work with
com.acme.foo 1.0.2 & 1.1.0; however we also know that we have a breaking change if we move to com.acme.foo 2.0.0 without appropriate updates to
Don’t Aggregate Dependencies!
OSGi versioning is on packages, not on bundles. The reasoning for this is simply that Bundles are an ‘‘aggregate’’ artifact and so must move as fast as the fastest moving exported packages they contain.
Lets pretend for the moment that this is not the case, and that we only version Bundles.
Consider a scenario where a bundle contains just two exported packages
bar. A change is applied where,
foois not changed
barhas a major change.
To reflect this the bundle version must also have a major change. However this now requires an unnecessary updates for bundle that only have an actual dependence on
foo. Aggregating dependencies in this way increases the fan out of the transitive dependencies; rapidly resulting in brittle composite systems where all of the constituent parts must be simultaneously updated.
Note that exactly the same problems applies with respect to REST based Microservices and relying on container image versions: yet another reason to use OSGi as the basis for modular Microservices.
Where is this information kept?
So packages may be shared between Bundles, and those that are, are versioned: this information recorded in the package’s directory via the
package-info.java file is for the
dao-api package in the microservices example.
The bnd plugin will pickup this information and add it to the OSGi Bundle manifest. As a developer you should change the version information in
package-info.java when you make changes to the package content.
Note - This is automatically managed if you are using Bndtools.
Bndtools documentation provides further information on Semantic Versioning, and how Bndtools simplifies the management of this.