Wiki

Clone wiki

limeds-framework / Guide to Versioning

Table of Contents

What is versioned?

In LimeDS versioning is applied both to the components as to the deployment units that contain these components. So in summary, the following applies:

LimeDS supports two deployment units:

Java-based OSGi Bundles

The version of the deployment unit is the OSGi Bundle version. The bundles can contain LimeDS components in the form of Java classes that implement the FunctionalSegment interface and are annotated with @Segment.

The version of each such component is derived from the version of the package that contains the component class. This is done at compile-time using the LimeDS BND plug-in: we use BNDTools, which is able to interpret package-info.java files that are present in the package. These files can define a version number using the OSGi Version annotation, for example our LimeDS API package has the following package-info.java file:

/**
 * This package contains the public Segments that together form the LimeDS REST API.
 */
@org.osgi.annotation.versioning.Version("0.2.0")
package org.ibcn.limeds.api;

E.g. the above file will result in all Segments in the package having 0.2.0 as version number.

It was a deliberate design decision of having a single version for the whole package, as we see the package as a single API unit and it makes sense to map a Java package to a LimeDS Slice conceptually.

Visually defined Slices

A Slice definition in LimeDS created using the editor behaves similarly to an OSGi bundle package: it can contain Segments (whose ids are prefixed with the Slice name) and has a single version number which is at deploy-time applied to all the containing Segments.

The difference is that the Slice definition is also the deployment unit, so there is no need for an additional deployment version number (like the Bundle Version number for LimeDS OSGi bundles).

The Slices created by the editor are stored as JSON files, but can be exported to OSGi bundles so we can later deploy and resolve them in the exact same way as the Java-based bundles.

What versioning strategy should I use?

The LimeDS version resolvement system works best when applying the Semantic Versioning strategy, which can be summarized as follows:

Given a version number MAJOR.MINOR.PATCH for the Slice definition or bundle package, increment the:

  • MAJOR version when you make incompatible API changes (e.g. A Segment was removed, or its input arguments have been altered)
  • MINOR version when you add functionality in a backwards-compatible manner (e.g. A new Segment was added, or a query parameter was introduced for a HTTP endpoint)
  • PATCH version when you make backwards-compatible bug fixes (e.g. A Segment that returned wrong results from time to time was fixed)

Important: even if you are not applying semantic versioning (which we don't recommend), LimeDS expects to see this version format. Suffix labels (e.g. 1.3.2-prerelease) are NOT supported for the sake of not over complicating the LimeDS versioning systems.

How does versioning affect dependencies/links?

The versioning of LimeDS components is only useful if the code that is depending on these components can specify with which versions of the components it is compatible.

Defining dependency versions in Java

Dependencies on Segments in LimeDS Java components are specified using @Link annotated fields. Use the version attribute to declare compatible versions for the dependency, for example:

@Link(target="org.ibcn.limeds.api.ComponentLister", version="1.0.0")
private FunctionalSegment componentLister;

The above will make sure that only a ComponentLister Segment with version 1.0.0 will be injected in the dependency field. Of course, this approach is not really flexible in the real world. That is why LimeDS supports version ranges as well:

Version ranges using wildcards

E.g. declare that the dependency supports all 2.3 versions:

@Link(target="org.ibcn.limeds.api.ComponentLister", version="2.3.*")
private FunctionalSegment componentLister;

Version ranges using carets

E.g. declare that the dependency supports all versions from 1.3.2 to the next major version:

@Link(target="org.ibcn.limeds.api.ComponentLister", version="^1.3.2")
private FunctionalSegment componentLister;

Currently LimeDS only supports the caret operator on the MAJOR version identifier.

Defining dependency versions in the Slice definition (using the editor)

When creating Slices in the Visual Editor, the version expression of dependencies are automatically generated according to the semantic versioning principles. E.g when drawing a dependency link from ComponentA to ExternalComponentB (version 2.1.0), an expression is generated that deems all versions between v2.1.0 and v3.0.0 (exclusively) of ExternalComponentB to be compatible with the code in ComponentA.

In future releases, we will add options to the Visual Editor to customize the version expression of linked dependencies.

Note: when depending on Segments inside the same package, you might want to depend on the exact version that is being exposed by the package. For this you can use the Link.USE_PARENT_VERSION constant in the Link annotation version attribute, for example:

@Link(target="org.ibcn.limeds.api.ComponentLister", version=Link.USE_PARENT_VERSION)
private FunctionalSegment componentLister;

This will enforce the dependency to always be bound to the Segment provided in the same package. This is the default behaviour for links between Segments declared in the same Slice!

Note: when using @Service to include OSGi dependencies, we fall back to the default OSGi behaviour were the potentially bound services are dependent on the compatibility of the packages (see OSGi documentation for more info).

How does versioning affect factory instances?

When dropping a factory Segment in a Slice LimeDS will try to generate instances based on the provided configuration for each available factory version. (This might fail if config properties were changed between factory versions). Each successfully created instance will have the same version as the factory that created that particular instance.

In addition, it is important to know that the component created in the Slice (by dropping in the factory), will be associated with the factory version selected by the user. E.g. when dropping in storage.defaultFactory (version 1.3.0) in order to create a query Segment called test.Queryer, will result in test.Queryer being deployed as version 1.3.0.

So when creating dependencies in the Slice on this test.Queryer version, the Visual Editor will automatically make sure that all versions of this factory instance are accepted up until version 2.0.0 exclusively.

This way users of factory instances have a reliable way of making sure that their Slices will not break when updated versions of the factory are introduced (if the factory providers stick to semantic versioning).

How does versioning work in combination with the HTTP endpoints?

LimeDS allows multiple versions of the same Segment to be active at runtime, even if these versions expose the same HTTP endpoint. LimeDS solves potential conflicts by always redirecting HTTP requests to the latest version of the Segment that can be found.

If clients want to use a specific version of a HTTP endpoint, they need to specify the expected version range by using the LimeDS custom http-api version header. Wildcards and caret ranges are also supported here!

For example, the following HTTP GET would output data from the latest version of the WeatherProvider Segment:

GET /weather HTTP/1.1
HOST: someHost:8080

We could also specify that our client is only compatible with major version 3 of the WeatherProvider by sending the following request:

GET /weather HTTP/1.1
HOST: someHost:8080
x-limeds-api-version: ^3

Updated