‘OSGi Demystified’ is a series of articles addressing common OSGi issues in CICS. We offer insight into OSGi, discuss best practices, and provide setup and configuration advice. This is the third in the series of section 1, to see the previous post discussing EBAs and OSGi bundles, follow this link.
- The differences when creating your JAR as an OSGi bundle.
- The misconceptions of OSGi bundles and application updates.
An OSGi bundle is a JAR
There’s absolutely nothing to lose by creating your JAR as an OSGi bundle – even if ultimately you don’t deploy it to an OSGi environment. The key difference is that an OSGi bundle contains more meta-data in the MANIFEST.MF in the JAR. The meta-data offers at least three important pieces of information; a unique-id (Bundle-SymbolicName and Bundle-Version), a set of dependencies (Import-Package), and the external interface (Export-Package). When not deployed to an OSGi environment, the meta-data is simply ignored.
OSGi bundles allow versioning of code
Beware! This statement can give rise to misconceptions. The primary capability here is about allowing multiple versions of the same package/class to be available within the same runtime. Two different applications might have different version requirements on the same library. OSGi allows both versions to be deployed simultaneously, and both applications can be satisfied. However, take note: multiple version support does NOT automatically provide seamless UPDATE of applications…and that leads us directly to the next point.
OSGi bundles do NOT provide dynamic update as well as you might expect
The misconception here is that you can deploy a new version of an OSGi bundle to the OSGi environment and everything magically starts using it. That could be true, but there are several ‘it depends’ to navigate first.
For example, it depends on whether you rely on bundle-wiring (the act of resolving Import-Packages) or whether you are using OSGi services. If you are using bundle-wiring, it depends on where and when the previous version was referenced. If some other OSGi bundle (B) uses your OSGi bundle (A) and successfully resolves against it, OSGi will NOT resolve to the new version (A’) by default. If it did, there is a risk of breaking a working application. If yet another OSGi bundle was installed (C), it might resolve to the new version of your OSGi bundle (A’), or it might resolve to the older version (A) – it is entirely dependent upon the way in which Import-Package dependencies are expressed. You can of course control the dependencies by judicious use of the ‘version’ attribute on the
Import-Package statement and by applying minimum versions and version ranges, but that’s hard work and very error prone.
To cut a complex set of interactions short, if you are using bundle-wiring (Import-Package) to dynamically update your applications, then you’ll have to be extremely careful with the order in which you install the new versions, you’ll need to be diligent about incrementing the OSGi bundle versions, and smart enough to figure out the right dependency ranges to ensure the whole chain is refreshed according to your wishes. Our advice: don’t do it. Use OSGi services instead (see later). Alternatively, if you don’t mind a little downtime, you could always disable and re-enable the JVM server!
Version ranges and semantic versioning are good practice
Regardless of whether you use OSGi services or not, you should employ a suitable bundle/package versioning scheme allowing you to distinguish between bug fixes, updates (new function), and interface breakage (API incompatibilities). OSGi semantic versioning does just that, we would encourage you to follow that model for your OSGi bundles. A useful summary is in the OSGi semantic versioning paper from the OSGi Alliance.
Knowing how to version your OSGi bundles is only half the story though. Using version ranges in your Import statements on top of semantic versioning can be the difference between constant resolution problems with hidden breakages versus smooth transitions. For example:
a) Import-Packge: com.myorg.package1 b) Import-Packge: com.myorg.package1;version=1.0.0 c) Import-Packge: com.myorg.package1;version=[1.0.0, 1.0.1) d) Import-Packge: com.myorg.package1;version=[1.0.0, 2.0.0)
Using (a) any version might match and it could walk you right into the classic pitfall of resolving against higher major versions. Doing so has the potential to break you at runtime because of interface changes. Using (b) any version from 1.0.0 upwards might resolve and you incur the same problems as (a). Most developers end up with such a configuration because it’s usually the default tooling generated configuration. Using (c) imposes a very restrictive match, the curved bracket implies higher versions are excluded. Using this approach requires updates and redeployment of the application for every bug-fix in your chain of dependencies. Using (d) is a happy medium. Bug fixes and API additions are tolerated without change. If there is an API breakage the OSGi bundle fails to resolve and protects you from a potential runtime failure.
The next instalment of OSGi Demystified succinctly explores the ins and outs of OSGi services. Read it here!