OSGi – Benefits and Drawbacks
OSGi for Enterprise Web Applications
OSGi has been around for more than ten years and has gained some popularity through its use within the eclipse eco-system. With a strong focus on modularity it seems to be the right technology for enterprise applications where modularity and dynamic extensibility are particularly desirable. More than any existing approach this could be a solution to keeping software extensible and maintainable at the same time allowing for a truly agile development process.
With this in mind, we started to delve deeper into OSGi and how it could benefit our software and our development process. We were a bit wary, however, as OSGi has not gained ground in the enterprise world yet, despite its being around for quite a while and although there are several OSGi-ready application servers. This post tries to unveil some of the issues with OSGi and its possible usages. It is neither an introduction on the subject nor does it cover the details of OSGi.
Main Concepts of OSGi
OSGi’s main layers of abstraction are bundles and services. Bundles encapsulate a set of functionality and provide it via services to other bundles. This is done by using a ClassLoader per bundle that provides the classes needed to the bundle and to its dependencies. Basically the ClassLoader has private packages only visible within the bundle and public packages that are visible to the outside world. Services can be registered at a ServiceRegistry and can be discovered by other bundles without knowing their implementation. This is the main concept allowing for a very modular application architecture.
As each bundle has its own ClassLoader bundles may exist in different versions. This allows bundles with dependencies to different versions of the same bundle to coexist. Bundles may be started or stopped or updated at any time. Through this the application’s functionality can be adapted to changing requirements over time without downtime. On the other hand the existence of a service’s implementation can never be guaranteed by the system. Summing up we have three major concepts that have their own benefits as well as their own challenges.
- Modularity: Modularity is deeply intertwined with one module’s code visibility to another module. OSGi handles this by hiding classes of one module from other modules. This is at the heart of the modularity concept of OSGi and causes the first set of challenges.
- Versioning: Versioning allows different versions of the same bundle to peacefully coexist. Each bundle has to declare which version of a package or bundle is needed. Through this bundles may use different versions of a class or package.
- Runtime-Flexibility: The most interesting as well as the most demanding feature of OSGi is its runtime flexibility. Services and their implementation can be exchanged, removed and added at any time. Theoretically this can be done with no downtime of the system.
Each of these concepts, however, comes with its own challenges. Application architects should therefore initially determine which of these concepts is really needed and than design an architecture accordingly.
Implementing an Enterprise Web-Application with OSGi
It is a rather trivial statement that an architecture should suit the requirements of a software system. Yet when we started out with OSGi we were a little blinded by all the promises of hot code swapping as well as allowing multi-tenancy with different versions of bundles. We started out with BND-Tools and Apache Karaf and chose ZK as our frontend framework and JPA as our persistence layer. This turned out to be quite challenging as ZK loads resources through the ContextClassLoader, which needed to know all resources ZK might need. JPA on the other hand does some heavy byte code manipulation at loading time of a class. In OSGi this is handled differently and thus the OSGi specification states, that there cannot be cross bundle entity references.
Handling the User Interface
We saw two ways of dealing with the issue of providing resources to the ZK framework. We could either delve deeply into the ZK framework to make its class-loading OSGi-compatible or we could use a bundle with an HTTPServlet responsible for setting the right ContextClassLoader and then make sure that every class and resource needed by ZK is visible within the ClassLoader. After a couple of days we chose the latter approach, as modifications on the ZK-framework would have been too broad otherwise compromising its updatability. Another issue with ZK is its component caching which occurs once at startup time. Adding new UI components thus meant restarting ZK as a whole. Both were acceptable although this was the first time we abandoned the path of a purely service-oriented approaches.
Handling persistence
Persistence was another challenging issue. JPA with OSGi required us doubling all classes that were to be used across bundles. This forced us to use interfaces for all entities, as their classes would be different. The main problem here was for the developers to write the correct code and to maintain the configuration files. Methods like hashCode and equals usually need to be rewritten which might be non trivial if inheritance is used for entities that exist in multiple bundles.
Versioning and Runtime-Dynamics
At this point we did not even consider the issues of versioning and runtime-dynamics. These bring further issues with them. Allowing for different versions of a service requires some place to decide which version to take, as there usually should not be a dependency on the service implementation. Either the API would change too, so that the dependency resolver handles the issue for us, or their might only be one service for one API. If the latter holds, we can even go a step further and declare each service providing bundle a singleton bundle, which is OSGi’s way of saying: there can only be one.
Things get even worse with runtime-dynamics. Its main advantage is, that a bundle might be replaced during runtime at any time (i.e. hot code swapping). This is great for development and allows a bundle to be replaced once its code compiles. But this also has some serious issues the most challenging of which is memory leaks. By replacing a bundle its ClassLoader is becoming stale. Yet this does not mean that its classes are removed. This renders the use of the HTTPSession much more difficult if not obsolete.
All objects in the session should be of a dependent package of the servlet (this obviously holds for the java.*-packages) or they need to be removed once a package is shutdown. Otherwise they stay in the session. For frameworks that make extensive use of the session such as ZK this quickly becomes a serious issue. If a servlet puts the result of a service-request into the session and the result’s class is not in the dependency tree of the servlet, there is likely going to be a leak.
OSGi – Key Takeaways
Modularity and Class-Loading
Class-loading is the most obvious challenge when using OSGi. Frameworks using dynamic class-loading like ZK and many others are difficult to use as the ContextClassLoader needs to be aware of the resources that the framework needs at runtime. Usually a BridgeClassLoader needs to be setup to pretend to the framework that everything is as expected. If classes are referenced from textual input files, the framework needs to know the bundle of the text file to set the right ClassLoader. An omnipotent ClassLoader on the other hand needs to make sure that private Classes invoked from a text file are also visible. This breaks the strict encapsulation of modules normally provided by OSGi.
Things get worse when frameworks require the knowledge of all resources of a particular kind beforehand to initialize some registry or cache. Due to the dynamic startup order of OSGi-bundles, initialization might not be trivial anymore.Persistence with JPA is another troubling issue. Here it might be useful to investigate alternatives to JPA. We favor jooq here, although mybatis and JDO might be worth considering, too. Class-loading needs to be solved in order for OSGi to be used as the basis of a modular architecture. It might be worth checking if parts of the system can stay monolithic. It might be ok to keep the presentation layer as a set of dependent modules and organize services around it. In this case it might be worth checking Virgo or JBoss as the basis of the application, where OSGi services can be deployed and looked up from the web layer using JNDI.
Versioning
Versioning seems to be very promising at first particularly with multi-tenant-aware systems in mind. Yet multi-tenancy-awareness can only be reached on an application level, not on a framework level. This is due to the fact that according to the specification there is a 1 to 1 relationship between a bundle in a specific version and its ClassLoader. Thus having different services for different tenants requires a bundle to be loaded more than once with different symbolic names (e.g. scoping in Eclipse Virgo). Solving this on an application level would require some workaround (e.g. the use of ThreadLocal variables) to indicate the current tenant. Creating another thread would have to be done via the framework (as in RAP).
The second issue with versioning is that different versions of a service might not be detectable by the framework. Thus a problem arises when a previous version of a service has the same API but behaves differently (e.g. does not provide security checks etc.). Thus it is usually sensible to flag bundles that provide services as singleton bundles. Versioning is therefore left for libraries that do not provide services.
Runtime-Flexibility
For agile development processes runtime-flexibility is particularly interesting as the running system can be amended until it meets changing requirements. Yet runtime-flexibility comes at the cost that most frameworks can no longer be used. Even worse, a lot of patterns have to be reevaluated. This is the case because with a bundle update a new ClassLoader provides the same classes. Or to put it differently, once a bundle is shut down, all classes it provides together with their objects become stale and they remain stale when the bundle starts up again. To illustrate the consequences, imagine an object in a HTTPSession of a class provided by a bundle updated. This class and thus the object becomes stale and should be replaced, making it very complicated to use objects in the session that do not have a class in java.*. The same holds for configuration objects or listeners. Imagine collecting all services together with their configuration and saving those in a session. From these configurations the application’s UI is built. This is no longer possible unless all configuration objects are proxied and the proxies are notified once a bundle is shut down. This is particularly worrisome since a developer is not notified about the memory leak he is about to open. Even worse stale objects might be referenced that lead to mysterious application behavior.
Complex cross-bundle in-memory models are thus really challenging. If every request is mapped to the database or if every bundle has its own model with no outside dependencies, the dynamic might not be too tricky but once a global model exists in memory this can easily become a major challenge, because every part of the model needs to be serializable and it needs to automatically serialize itself on bundle shutdown and deserialize itself on bundle startup. Additionally every listener needs to be dereferenced and rereferenced again. This can only be overcome by strictly adhering to the service model where every model part would have to globally register a listener service on a whiteboard that gets informed on every change of the model. If this change affects part of the bundles model, it has to react accordingly. Extending classes cross-bundle should thus be avoided since it would require objects to migrate classes if the extending bundle is updated. All this puts a lot of responsibility on the developers shoulders, as memory leaks and stale objects could be created by simply „programming as usual“.
Usage Scenarios
Now what should be made of this discussion? First of all we strongly recommend not to use the hot code swapping in production and probably not even at runtime as it most likely leads to stale objects and strange application behavior unless you are designing a system from ground up to cope with the kind of challenges mentioned above. Secondly different versions of the same bundle are most likely suitable for libraries and much less for services. The advantages of a strongly modular system come with the disadvantages that frameworks might no longer work. This is most likely the case when classes are referenced from text files or when byte code manipulation occurs
Maven as Alternative
With all the drawbacks, it is understandable why most projects choose a maven based project setup. As runtime-flexibility with hot code swapping comes at a very high cost, it is highly questionable if this is needed in most projects. Thus the main advantage is a strong modularity concept. Given the right architecture, this can as well be achieved with maven by implementing the concepts behind OSGi (i.e. registries and whiteboards) in a maven based environment.
Conclusion
If you have runtime code change as a requirement, OSGi is probably the only possible way to achieve this using Java. But this is also the most difficult system to develop.
Obviously it is possible to use OSGi in a rather static fashion by requiring a system restart every time the application or at least the UI changes. This, however, would question most benefits of OSGi since this makes the need of different versions of a bundle also questionable unless a system really requires lots of libraries that are not compatible with each other. The corresponding services can still be supplied to the web application using OSGi as most JEE-containers provide OSGi as an alternative approach. If OSGi is used as a modularity framework only than an undefined starting order of the bundles causes much more implementation overhead than is actually needed for this specific scenario.
If a dynamic usage of OSGi is the goal, however, than most parts of most UI-Frameworks need to be rewritten as they all heavily depend on the HTTPSession. Replacing a UI bundle would require all elements within the session to essentially change their class, i.e. to become serialized and deserialized again. Out of performance considerations it is impossible to keep the session serialized the whole time. Thus there needs to be some event handling that serializes every object of some bundle‘s classes once this bundle becomes deactivated. This is far from trivial to implement in most frameworks.
Thus our conclusion: OSGi is not yet worth the hassle, unless someone provides a UI-framework that handles the object serialization of bundles correctly to allow for the dynamic usage of OSGi without memory leaks.
Outlook
OSGi is probably the only JVM-based technology allowing for hot code swapping and multi-tenancy with code in different versions. Yet using Java for this requires writing an lot of code for cleaning up stale objects so that a lot of the benefits of automatic garbage collection are rendered useless. For this reason we currently evaluate programming an abstraction above Java that handles the cleanup process, when a bundle gets deactivated. Models are defined using this language and can then be used by services. As this is work in progress, an in depth evaluation has not yet been done. Nonetheless this approach would probably have to create a new UI-framework. If we go this way, it would be particularly interesting to have an OSGi equivalent on the client side such that the UI gets updated automatically and the corresponding scripts get executed once new modules are activated.