In part 1, Aslam Khan, technical director of PBT Group, introduced us to OSGi. Today, in the final part, he completes his argument about OSGi's applicability in the enteprise. In part 1, he discussed OSGi's problem domain. There, he explained issues surrounding dynamic class management and how OSGi responds to these issues. In this final part, he tackles the two other problem areas outlined previously: the lack of versioning of Java classes and the lack of modularity in the context of classpath hell. How does OSGi fill these gaps? Finally, how ready is OSGi to be used for real? Aslam also answers this question. -- Geertjan Wielenga, JavaLobby Zone Leader
A bundle is not a special artifact in OSGi. It is merely a JAR with additional headers in its manifest. For example:
# Indentification headers
Bundle-Name: Sample Service
Bundle-Vendor: Samples, Inc.
# Class Path
# Bundle Lifecycle management
Notice that each package in these headers also specifies a version range. Looking carefully, notice also that there is manifest header for the version of the bundle. When binding between bundles occurs, the version ranges are used to resolve valid bindings. In Illustration 1, the class foo has two versions running concurrently. foo version 1.0.0 is exported by bundle A and foo version 2.0.0 is exported by bundle C. However, bundle B has a dependency on foo version 1.0.0 only, and is, thus, bound to it alone.
In fact, even finer control of bindings can be achieved by using an arbitrary set of mandatory attributes which may be used as a simple way to limit package visibility.
This simple mechanism of versioning of classes opens up an excellent opportunity to manage backward compatibility. Simply put: it is acceptable to have more than one instance of the same JAR, differing in versions, installed at the same time and neither will take precedence over the other because the class loader no longer resolves dependencies on a first-found basis.
When class versioning is combined with dynamic loading and unloading of classes, the management of backward compatibility in large applications is a whole lot more achievable. Simply using OSGi does not solve backward compatibility, but it does make management of backward compatibility easier. To solve backward compatibility, sound principles of API design and the like is still necessary.
Modularity is a deceptive problem. One would think that this age old requirement would have been cracked a long time ago. In non-trivial, yet relatively simple applications, achieving modularity is not difficult. The easiest way of achieving modularity in simple applications is to use horizontal modularization which maps directly to the layers in the application stack. For example, in an application that implements the Model-View-Controller and Data Access Object patterns, the typical modules consist of model classes, view classes, controller classes and DAO classes.
In each of these layers (or modules), interfaces will be exported and implementation specific bundles deployed to fulfill these dependent interfaces. The upside of doing this in OSGi is that it is possible to switch, for example, a Hibernate implementation of a DAO interface with an iBatis implementation of the same DAO interface. Again, this is done dynamically, while the rest of the application is still running.
Modularity starts getting complicated as the application increases in size or the number of applications in an enterprise system increases. The simplicity of mapping modules to layers is still useful. However, as the overlaps and intersections in the usage of classes in different parts of the application or in different applications in the system increases, the potential for class loading disasters increase. This is vertical modularization and it is certainly not trivial.
Even though it may seem like a poor architectural choice, carrying duplicate code at run-time in separated contexts is one solution. In this approach, there is still only one copy of the source code in version control but a fairly intelligent build system is required that will pull together parts from the source tree to create the necessary run-time artifacts. Arguably, a flatter source tree may prove to be a lot easier than a very deep source tree. In essence, this approach is a highly configurable build-time assembly of modules and requires a team to make mental peace with having duplicate binaries running concurrently. And, yes, we all know about duplication issues, but there are times when this may well be acceptable, even for a short period in the life of your system.
The opposite of configurable build time assemblies is configurable run time assemblies. In this approach, OSGi is a particularly good match. As discussed earlier, the service register of OSGi allows for discovery of modules that satisfy the dependencies of other modules. But we have still not progressed; we still don't know how to create effective modules. Unfortunately, there is no golden recipe.
One approach to discovering modules is to use strategic design from Domain Driven Design (DDD). In strategic design, bounded contexts within the problem domain are created and the touch points between contexts are identified. This is actually a skewed use of bounded contexts because a bounded context can be translated to another bounded context wherein the content of the context may have been transformed in the translation process. Nevertheless, strategic design is a reasonable starting point for creating modules. This occurs at a fairly high level in the architecture and yields modules that are relatively coarse grained, but closely aligned with the problem domain.
To move to a finer granularity, DDD patterns such as aggregates, repositories, factories can be used. Although, these patterns deal with the life cycle of domain objects, the patterns themselves offer good candidates for modules. Be guarded against treating patterns as modules because this creates a distraction which starts deviating the solution away from the problem domain. Instead, consider, for example, combining aggregates and their factories and repositories such that the combination still reflects a part of the problem domain precisely.
For an intermediate level of granularity the module concept in Domain Driven Design may also be employed. DDD modules are chosen such that the modules tell a story of the system. Each module is cohesive and is very loosely coupled to other modules.
Regardless of the granularity of the modules, DDD is a strong contender for producing clean vertical modules. Once these modules are designed, they can be implemented as bundles and dropped into an OSGi implementation directly.
Often modules are created at the outset of a design, reflecting the common parts in the system, and is largely left untouched. This is not far removed from the massive-up-front-database-design-which-can-never-change approach. Commonly, the internals of the module may change over time, but the actual set of modules rarely change. DDD encourages module refactoring. This is more painful than class refactoring but the run-time assembly of modules, backward compatibility and class versioning capabilities of OSGi does open a pathway for gradual refactoring of modules as the system evolves.
In essence, the combination of DDD for the design of modules (build-time assemblies) and OSGi (run-time assemblies) offers an elegant approach to tackle the modularity problem.
Can you use OSGi in your applications today?
Without a doubt, yes! OSGi is mature and offers software architects and developers the opportunity to solve some tough problems. The dynamic nature of the OSGi runtime, its excellent class versioning capabilities and the built-in service register of modules for dependency discovery creates an ecosystem for POJO based development that is highly productive for enterprise projects.
The Spring Framework Dynamic Modules for OSGi sub-project simplifies OSGi development even further by using the familiar Spring application context to register services and specify dependencies between bundles, using the familiar Spring inversion of control/dependency injection container.
Finally, strategic design principles and other patterns from Domain Driven Design can be used to design a set of vertical modules that can be used for build time assemblies and run time assemblies.
However, OSGi has hurt some proven solutions as well. The most notable is that of aspect orientation. Inside a bundle, AOP is easily achievable. However, applying aspects across bundles is still a problem and projects such as the Eclipse Equinox Aspects project show strong promise.
Overall, OSGiTM – the dynamic module system for Java, deserves the attention it is receiving. With the formation of the OSGi enterprise working group, it is certainly going to get stronger in the short to medium term. It is definitely worthy of its place in the enterprise application development space.