OSGi is a standard applicable wherever the option to add/modify/remove functionalities within a single Java Virtual Machine is required, without service interruptions. It is encountered, among others, in such SDN (Software-Defined Networking) controllers as ONOS [1] (which we use in the SDNbox and SDNcore solutions at EXATEL) and OpenDaylight[2].
After 20 years of its existence, we already know that this concept, though innovative in the past, has not found any application outside of niche, specialized apps. In 2013, only (as many as?) 10% of all the libraries published in the Maven Central Repository (the most popular repo with Java dependencies) were bundles[3]. But let’s go back in time, to OSGi beginnings.
It’s May 2020. Open Services Gateway Initiative publishes its first version of OSGi[4] specification. It begins with the words: “The primary goal of the OSGi service framework (“Framework”) is to use the Java (TM) programming language’s platform independence and dynamic code-loading capability to make development and dynamic deployment of applications for small-memory devices easier”. What should OSGi captivate us with? What should have been the fates of this standard, for it to be commonly used in 2020?
OSGi enables managing the lifecycle of individual components
A bundle is a module with explicitly declared dependencies (import-package) and explicitly declared API (export-package), such as other components could use. OSGi framework provides each such module with a separate class-loader, which is why each module has an independent lifecycle. This enables free update/deactivation of selected components, without the need to stop the entire application. Moreover, since it happens dynamically, it is relatively easy to develop an app that other users could expand with their own code and execute, treating the developed solution as a framework.
OSGi or a missing access modifier in Java
One of the most important practices when developing one’s own library is creating a well-defined API and hiding implementation details. Originally (up to Java 8), four access modifiers were available to control this:
- public – this modifier denotes the API,
- protected – this modifier suggests that a library user can overwrite the code by expanding appropriate classes,
- package-private – elements are visible only within a single package,
- private – elements visible only inside the class.
At some point, a module code becomes large enough that we want to freely divide it into numerous packages, while maintaining its assumption that only API classes are marked as public. What is missing is only a single access type that can be described as “modular”. OSGi solves this issue through changing the method of defining inter-modular contacts. API is defined through adding the “Export-Package” header in the MANIFEST.MF file. These packages contain classes available for other bundles. Subsequently, the “public” modifier only means that it has visibility within a single bundle (until the class-containing package is not declared as exported in the manifest).
OSGi actively utilizes semantic versioning
MAJOR.MINOR.PATCH[5] versioning helps users (administrators, software developers) to verify if individual application module versions are compatible with each other. From the perspective of a person updating only one bundle by increasing the version number:
- PATCH means that with uploading a newer version, the user receives minor bug fixes, and updating dependent components is not required.
- MINOR means that the user expects receiving new functionalities with backward compatibility, therefore, changing the version of dependent components is not required (except for a situation when the user wants them to utilize the new functionality).
- MAJOR means an incompatible change in API, hence, all components that depend on the corrupted bundle should also be updated.
OSGi obliges the saving of this contract in the MANIFEST.MF file through defining the version range for each imported package separately in the “Import-Package” header. Validation takes place at every attempt to install a new bundle.
Application examples and associated challenges
OSGi was developed for large web apps, and currently they are the primary application for Java. Of course, nothing prevents using OSGi in microservices or, if you decided on a monolith, setting up its multiple instances. This induces a situation wherein only (and as much) scalability is an issue completely separate from OSGi. Let us review the examples below.
Example 1 – designing a new web app.
You are wondering which of its components will be used most often, how to scale their use depending on the number of users, and how to update the entire solution, without interrupting the service. A general solution is a modular app that can be based on microservices, perhaps a clustered monolith. These solutions are often achieved without the OSGi standard. When, after designing, you are wondering whether to also add OSGi modules, it often turns out that this can create a lot of additional work, without generating much profit. Bundles require additional configuration, narrow the application server selection and they do not always match the chosen technology stack (e.g., Spring).
Example 2 – you have a ready app compatible with the OSGi standard that you want to transform into microservices.
It turns out that, despite the nomenclatural resemblance (OSGi has nanoservices, and you want microservices[6]), you are faced with a lot of work to isolate individual modules as separate applications, establish a method of communication between these modules, run various virtual Java machines, and redesign the current application installation method.
There are no simple methods to develop an application
OSGi provides a fully structural approach to executing individual application components. But what about building them? Will the effort put in creating bundles with explicitly defined API’s and links between the modules (with package and version accuracy) pay off with faster builds owing to the possibility of detecting which modules have been changed and require reconstruction and parallelization of independent change build? Well, the answer is, unfortunately not.
Tools that unlike traditional ones (such as Maven, Ant or Gradle) enable easy incremental builds have appeared only relatively recently. They include:
These tools, although exhibiting good cooperation with OSGi, were not developed with this framework in mind. There are no generators that will create a build manual comprehensible for the chosen tool, based on bundle configuration. Moreover, both Buck, as well as Bazel are relatively young, therefore, when using them, one may encounter various unusual problems that are not found in more mature solutions.
Lack of support for popular frameworks and libraries
As already mentioned in the introduction, only approximately 10% of all the libraries publish in Maven Central Repository are ready to use in an app developed within the OSGi framework. A software developer who decides that a dependency, despite not being a bundle, should be used in a project, can attempt to adapt it, for example, by creating shadow jar[9]. However, such a solution requires additional effort (to do it correctly, you have to analyse the dependencies used by the library you require, and discern which packages the API of your library is located within), and this is only possible in the case of simpler dependencies. For example, it is impossible to adapt the currently most popular Spring framework this way. Moreover, while there were attempts at integrating Spring with OSGi, such as Spring Dynamic Module (which later evolved into Eclipse Gemini Blueprint), today these projects are dead.
Java 9 with the Jigsaw project
The long-awaited option to define modules in pure Java appeared in September 2017 (which is more than 17 years after the first version of OSGi specification). It is not as extensive as in bundles (e.g., there is not lifecycle and dynamic loading[10]). It primarily deploys a new place for introducing access control (see: OSGi or a missing access modifier in Java). Despite it being the same idea, it is implemented differently. Configuring a module in OSGi does not make it a Jigsaw module either. There are plans to combine both solutions[11][12], however they have not still been implemented.
Summary
When working with OSGi, take a moment to reflect that this solution was once ahead of its time. One can suspect that if additional benefits (which were actually pretty close – with all the required information already present in the additional manifest headers) appeared together with OSGi deployment, such as the option for faster application build or easy transfer to microservices, this would be commonly applied standard today.
It should be noted that there are applications where OSGi is still indispensable, such as the aforementioned SDN controllers. However, even in these cases, it is planned to expand capabilities via newer technological stacks[13].
Sources:
[1] https://www.opennetworking.org/onos/
[2] https://www.opendaylight.org/
[3] https://blog.osgi.org/2013/09/osgis-popularity-in-numbers.html
[4] https://docs.osgi.org/download/r1/r1.osgi-spec.pdf
[5] https://semver.org/
[6] https://www.oreilly.com/radar/modules-vs-microservices/
[7] https://buck.build/
[8] https://bazel.build/baze
[9] https://icodebythesea.blogspot.com/2012/03/making-osgi-bundle-out-of-third-party.html
[10] https://www.zyxist.com/blog/zrozumiec-moduly-w-javie-9
[11] https://www.infoq.com/articles/java9-osgi-future-modularity/
[12] https://www.infoq.com/articles/java9-osgi-future-modularity-part-2/
[13] https://gonorthforge.com/the-next-generation-architecture-of-onos/