This page explores versioning of Tuscany modules and 3rd party libraries distributed with Tuscany. The description below assumes that each Tuscany module is converted into one OSGi bundle and that each 3rd party jar is converted into one OSGi bundle. The ongoing discussion on the shape of Tuscany distributions will impact versioning, but as long as entities that require to be versioned are available as OSGi bundles, these examples will apply in some form. At least for 3rd party libraries, we should definitely provide one bundle per jar to enable versioning that is compatible with 3rd party bundles from other OSGi repositories.

Tuscany and applications inside an OSGi runtime

In the earlier discussions, there was some confusion about whether we expect our users to view Tuscany as in Figure 1) or in Figure 2).

In the figure above, Tuscany is a black box (ok, a blue one) which has been OSGi-enabled at a fine grained level. Tuscany modules and 3rd party libs are all individual bundles, with proper versions, and multiple extensions can execute using different versions of 3rd party libs side-by-side. So Tuscany now has the full benefits of OSGi. From an application developer's point of view, Tuscany is a big blob that provides the Tuscany SPI/SCA API. Everything else is internal to Tuscany. If an application wants to use Axis, it will install a different copy of it. So applications will not accidentally use Tuscany's 3rd party jars. And Tuscany also will not accidentally use other 3rd party jars bundle-ized elsewhere. Tuscany's imports and exports are guarded with Tuscany-specific attributes in this case. IMO this is not good practice in OSGi - Tuscany is not being a good citizen.

In this case, apart from Tuscany SPI/SCA API, which we want others to use, other import/exports will look like:

Export-Package: javax.xml.bind;version="2.1";exporter="tuscany";mandatory:=exporter
Import-Package: javax.xml.bind;version="[2.1,3.0)";exporter="tuscany"

An user wishing to migrate to a different version of a 3rd party bundle will not be able to use a version from a different OSGi repository. An application can potentially use Tuscany's copy of a third party library, but only by explicitly specifying the "tuscany" attribute.

Figure 2) also uses the same set of bundles as Figure 1), but in this case there are no Tuscany-specific attributes. 3rd party bundles are potentially shared across applications and Tuscany when import version ranges match. Some of this sharing may not be intentional. Tuscany has to interoperate with bundles from elsewhere, and Tuscany's 3rd party bundles should interoperate with applications. For 3rd party jars which are used to share classes between applications and Tuscany, this is the only option.

In this case, import/exports in Tuscany as well as applications will look like:

Export-Package: javax.xml.bind;version="2.1"
Import-Package: javax.xml.bind;version="[2.1,3.0)"

OSGi runtime has the flexibility to choose appropriate bundles when multiple copies are present. IMO we should use 2) rather than 1).

Versioning bundles in Tuscany

Export versions

This is the easy part. Tuscany's own exports will use the Tuscany version (eg.1.2.1). 3rd party jars will export using their jar versions (eg. Jaxb-impl-2.6.1.jar will provide packages with version 2.6.1)

Import version constraints (and "uses" constraints on export statements)

How complicated can it be? We have version numbers across all jars and bundles and we want to put sufficient restrictions in place to use appropriate versions of jars. For small applications, versioning is not a big deal, and can be easily handled using defaults from tools. But for something the size of Tuscany with nearly 150 third party libraries which is used to assemble applications which may use some of these jars for other purposes, import versioning can indeed be extremely complex to support all possible scenarios. The level of sharing of classes required, the level of isolation required for side-by-side execution of multiple versions, the flexibility required for upgrading 3rd party libs and the actual version ranges that are compatible should all be taken into account to determine the optimum version range (for each of the 150 3rd party libs).

Let us start with a (simple) scenario:

I start building an application with the dependencies on the left in mind. Tuscany was built with the dependencies on the right in mind. I now put the two together. What classes from which classloaders do we actually see in the OSGi runtime? The answer is "It depends". Import versioning is critical in deciding what amount of sharing of bundles (and hence classes) is allowed between a) Tuscany modules and 3rd party libs, b) Tuscany modules and applications c) applications and 3rd party libs d) between 3rd party libs. Import versioning also defines the range of different versions of bundles that can execute side-by-side within a single OSGi runtime. And these two are conflicting requirements.

Broad version ranges in imports

This is the default used by most tools like maven-bundle-plugin, and other versions of 3rd party libraries from repositories like SpringSource also make version ranges in imports as broad as possible. OSGi spec says: "the import should be as unconstrained as possible to allow the resolver maximum flexibility". So what does this really mean?

Let's look at the example from Fig 3.

Tuscany can import Axiom API using an import statement like:

 
Import-Package: org/apache/axiom/soap;version="1.2.5"

This means that the version range of Axiom that Tuscany supports is "1.2.5 to infinity". Typically users will have only version 1.2.5 in their OSGi runtime, and that is the version that will be used. The application in Fig 3 however has another copy of this bundle which is at version 1.3. As shown in Fig 4, we now have Tuscany wired to Axiom version 1.3 (which may or may not be the desired outcome in this case). This sharing implies that App A and Tuscany can share Axiom classes. Similary Tuscany's extensions share Jaxb classes.

The broader the version range, the higher the class sharing that is enabled. Since we still have 3rd party libraries which rely on thread context classloaders which dont fit in with OSGi modularity/versioning, a broad range in import versioning reduces the risk of classloading errors arising from multiple definitions of a class in different classloaders.

Another scenario is upgrading one 3rd party library in Tuscany. An user wants to migrate Tuscany to using version 1.4 of Axiom. A flexible versioning system enables the user to just install the new version of Axiom, without any changes to Tuscany bundles. eg. An user can get Axiom 1.4 from SpringSource and easily run it with Tuscany without waiting for the next release of Tuscany that ships with Axiom 1.4.

But there is a negative side - as shown in Figure 4, we are preventing side-by-side execution of different versions of jaxb. Also we cannot easily prevent Tuscany being run with a newer version which may be incompatible if an application wants to switch to a newer version.

Narrow version ranges in imports

We can make version ranges in imports as narrow as possible eg. only support one version.

Import-Package: org/apache/axiom/soap;version="[1.2.5,1.2.5]"

The result is shown in Figure 5. Sharing of bundles only takes place for exact versions. More versions can now execute side-by-side. eg. Two Tuscany extensions can now run using different versions of jaxb. But these two extensions cannot share any jaxb classes.

Back to the scenario where an user wants to migrate to a later version of a 3rd party library like 1.4 of Axiom. The narrow version range in Tuscany imports essentially means that the user has to modify every single Tuscany bundle which imports Axiom to switch to 1.4 of Axiom.

So narrow version ranges can reduce class sharing and increase side-by-side execution, but that also make upgrades difficult, introduces more number of classes loaded in the system, and can result in classloading exceptions resulting from multiple definitions of classes. TCCL based classloading can potentially lead to exceptions.

Figure 4) and Figure 5) are showing the two extremes, the version range we want may be somewhere in between. eg. By using the version range [1.2.5,2.0.0) of Axiom, Tuscany will be importing any version of Axiom within this major version 1, and preventing importing of the next major version 2, which may contain breaking changes.

If you have actually read this far, you must be patient. I will try to summarize now.

Points to consider for versioning 3rd party libs

  1. What is the minimum version that Tuscany will work against? Can we assume that the currently used version is the lowest supported?
  2. What is the maximum version that Tuscany will work against? Can we assume that all the APIs we use will remain backward compatible? For a generic versioning scheme which doesn't involve looking at every single one of the 149 libs, the choices for max version are
    1. current version
    2. any minor version with the current major version
    3.  infinity
  3. What are the third party libs where classes are shared between applications and Tuscany?
  4. For  libraries in 3) what are the versioning requirements? Min version? Max version?
  5. Does Tuscany have extensions which require different versions of a 3rd party lib? If so these should use very narrow version ranges to enable the libs to coexist.
  6. Are there applications which require different versions of a 3rd party lib from Tuscany? If so, should we use very narrow version ranges to enable these libs to co-exist?
  7. Do we want to be consistent with bundles from other sources? SpringSource? ServiceMix? Any others?

Implementation steps?

  1. One bundle per Tuscany module, one bundle per 3rd party lib
    • Based on the discussion on aggregating modules into larger bundles, Tuscany module bundling may change.
  2. All Tuscany modules export packages at Tuscany version (eg. 1.4.0), all third party libs export packages at their jar version.
  3. All bundles with exports will also import those packages using the same version range as other importers  (OSGi best practice)
  4. Imports and "uses" statements in exports
    1. Minimum version: same as currently used version in Tuscany
    2. Maximum version - use defaults in the first instance for all 3rd party libs (ie. maximum version is infinity).
    3. Move to maximum version under current major version, Eg. Jaxb 2.1.6 will be imported using version range [2.1.6,3.0.0)
    4. Fine tune further to narrow down version ranges for specific third party libs as we learn more.

We can implement 1, 2, 3, 4.1 and 4.2 to start with since they are easy to do with current tooling.

  • No labels