OSGi technology is a set of specifications that define a dynamic component system for Java. These specifications enable a development model where an application is composed of several components which are packaged in bundles. Components communicate locally and across the network through services.
An application in this context represents the functionality desired by the organization. For example, an expense account reporting application or a payroll application. The goal is to make the application code as small as possible because that code is not reusable. It is the code that is unique for the application and usually highly coupled to a large set of components. That, however, works two ways. Since applications are not reusable extra dependencies are very cheap.
Components are the reusable building blocks, i.e., the bricks, the components provide the implementation code. Since components should be reusable, they should watch their dependencies because any dependency will be added to any application that uses this component.
The OSGi specifications enable components to hide their implementations from other components while communicating through services, which are objects that are explicitly shared between components.
Services are an innovation that OSGi brought to the table. Services are the reified links between components. In a well designed OSGi system, all links between components go through a service. Services have an API that is defined in a Java package. The API consists of classes and/or interfaces that are needed for the collaboration between the provider of the service and the consumer of the service. (Note that provider and consumer do not imply implementer/user of an interface).
This surprisingly simple model has far reaching effects for almost any aspect of the software development process.
A bundle is the OSGi name for a module, it packages the components with their resources. Bundles are explicit in their requirements on the environment and the capabilities they will provide to the environment. For example, a bundle will express what Java packages it needs in what version.
Components have been on the horizon for a long time now, however so far they failed to make good on their promises. OSGi is the first technology that succeeded with a component system that solves many real software development problems. Adopters of OSGi technology see significantly reduced complexity in almost all aspects of development. Code is easier to write and test, reuse is increased, build systems become significantly simpler, deployment is more manageable, bugs are detected early, and the runtime provides an enormous insight into what is running. Most importantly, it works.
OSGi technology was developed to create a collaborative software environment. We were not looking for the possibility to run multiple applications in a single VM. Application servers do that already. No, the problem were addressing was harder. We wanted an application to emerge from the collection of different reusable components that had no a-priori knowledge of each other. Even harder, we wanted that application to emerge from the dynamic assembly of these components.
For example, you have a home server that is capable of managing your lights and appliances. A component could allow you to turn your lights on and off using a web page. Another component could allow you to control your appliances via a text message. The goal was to allow functions to be added without requiring the developers to have an intricate knowledge of each other. This makes it possible for components to be developed and added and removed independently and without affecting the operation of any other function.
OSGi has a layered model that is depicted in the following figure.
- Bundles – Bundles are the OSGi components made by the developers.
- Services – The services layer connects bundles in a dynamic way by offering a publish-find-bind model for plain old Java objects.
- Life-Cycle – The API to install, start, stop, update, and uninstall bundles.
- Modules – The layer that defines how a bundle can import and export code.
- Security – The layer that handles the security aspects.
- Execution Environment – Defines what methods and classes are available in a specific platform.
The fundamental concept that enables such a system is modularity. Modularity, simplistically said, is about assuming less. Modularity is about keeping things local and not sharing. It is hard to be wrong about things you have no knowledge of and make no assumptions about them. Therefore, modularity is at the core of the OSGi specifications and embodied in the bundle concept. In Java terms, a bundle is a plain old JAR file. However, where in standard Java everything in a JAR is completely visible to all other JARs, OSGi hides everything in that JAR unless explicitly exported. A bundle that wants to use another JAR must explicitly import the parts it needs. By default, there is no sharing.
The code hiding and explicit sharing provides many benefits (for example, allowing multiple versions of the same library being used in a single VM), the code sharing was only there to support the OSGi services model. The services model is about bundles that collaborate.
To find out more about why modularity is so important, how it can reduce complexity and facilitate agility and evolvability read on here.
We needed the service model because Java shows how hard it is to write collaboratively with only class sharing. The standard solution in Java is to use factories that use dynamic class loading and statics. For example, if you want a DocumentBuilderFactory, you call the static factory method DocumentBuilderFactory.newInstance(). Behind that façade, the newInstance method tries every class loader trick in the book (and some that aren’t) to create an instance of an implementation subclass of the DocumentBuilderFactory class. Trying to influence what implementation is used is non-trivial (services loader model, properties, conventions in class name), and usually global for the VM. Also it is a passive model. The implementation code cannot do anything to advertise its availability, nor can the user list the possible implementations and pick the most suitable implementation. It is also a static model. Once an implementation hands out an instance, it cannot withdraw that object. Worst of all, the factory mechanism is a convention used in hundreds of places in the VM where each factory has its own unique API and configuration mechanisms. There is no centralized overview of the implementations to which your code is bound. In other words, a nightmare.
The solution to all of these issues is simply the OSGi service registry. A bundle can create an object and register it with the OSGi service registry under one or more interfaces. Other bundles can go to the registry and list all objects that are registered under a specific interfaces or class. For example, a bundle provides an implementation of the DocumentBuilder. When it gets started, it creates an instance of its DocumentBuilderFactoryImpl class and registers it with the registry under the DocumentBuilderFactory class. A bundle that needs a DocumentBuilderFactory can go to the registry and ask for all available services that extend the DocumentBuilderFactory class. Even better, a bundle can wait for a specific service to appear and then get a call back.
A bundle can therefore register a service, it can get a service, and it can listen for a service to appear or disappear. Any number of bundles can register the same service type, and any number of bundles can get the same service. This is depicted in the following figure, which is what is generally referred to as a broker pattern.
What happens when multiple bundles register objects under the same interface or class? How can these be distinguished? Firstly, in many cases it is not important to distinguish between individuals. Otherwise the answer is properties. Each service registration has a set of standard and custom properties. An expressive filter language is available to select only the services in which you are interested. Properties can be used to find the proper service or can play other roles at the application level.
Services are dynamic. This means that a bundle can decide to withdraw its service from the registry while other bundles are still using this service. Bundles using such a service must then ensure that they no longer use that service object and drop any references. We know, this sounds like significant complexity but it turns out that helper classes like the Service Tracker and frameworks like Declarative Services can remove the pain and the advantages are quite large. Service dynamics were added so we could install and uninstall bundles on the fly while other bundles could adapt. That is, a bundle could still provide functionality even if the http service went away.
We found that the real world is actually dynamic and many problems are a lot easier to model with dynamic services than static factories. For example, a Device service could represent a device on the local network. If the device goes away, the service representing it is unregistered. This way, the availability of the service models the availability of a real world entity. This works out very well in, for example, the distributed OSGi model (Remote Services) where a service can be withdrawn if the connection to the remote machine dies. It also turns out that the dynamics solve the initialization problem. OSGi applications do not require a specific start ordering in their bundles.
We found that the service registry significantly simplified application code because it handles so many common patterns. The effect of the service registry has been that many specialized APIs can be modeled with the service registry. Not only does this simplify the overall application, it also means that standard tools can be used to debug and see how the system is wired up.
OSGi services should be looked upon as a software design primitive. In the eighties objects were seen as weird curiosities as structs with function tables. Only when polymorphism, inheritance, and data hiding became design primitives in people’s mind did we start to reap the benefits. This is similar for OSGi services. They can be extremely lightweight (not much more than a Java Object) but have semantics that go way beyond what a plain Object can do.
Though the service registry accepts any object as a service, the best way to achieve reuse is to register these objects under (standard) interfaces to decouple the implementer from the client code. This is the reason for the OSGi Compendium specifications. These specification define a large number of standard services, from a Log Service to a Measurement and State specification. All these standardized services are described in great detail.
That said, the approach to decouple the API from the implementation pays off in even the smallest of problems. It is our experience that even trivial problems tend to grow over time. Separating the API from the implementation makes almost every aspect of the software development process simpler today and certainly for the long term. It is our recommendation that organization wide APIs are carefully managed by organizations.
Declarative Services & Configuration
Two of these standardized services in OSGi are the Configuration Admin service and Declarative Services (DS). Though these service are just a few of the many compendium services they have a special role. These services provide functionality that is very hard to evaluate from the outside because they have no counterpart in in other systems.
Declarative Services makes writing a service implementation as simple as writing a POJO with a few annotations. Though there are other systems that do similar injections as Declarative Services, these other systems ignore time and dependencies. By handling time and (dynamic) dependencies without any code overhead OSGi provides a toolbox that is as innovative as objects were in the nineties. Declarative Services has received a number of updates and enhancements with OSGi R7 including support for constructor injection, activation fields, enhancements to component property types, and a number of smaller enhancements.
In a similar vein, Configuration Admin can be used to not only configure service implementations, it can also control the lifecycle.
DS and Configuration Admin make it possible to create components that are completely configured through Configuration Admin, including their lifecycle and so no configuration API is required. This is a feature that has no counterpart in other environments but the value of this is hard to over estimate.
Bundles are deployed on an OSGi framework, the bundle runtime environment. This is not a container like Java Application Servers. It is a collaborative environment. Bundles run in the same VM and can actually share code. The framework uses the explicit imports and exports to wire up the bundles so they do not have to concern themselves with class loading. Another contrast with application servers is that the management of the framework is standardized. A simple API allows bundles to install, start, stop, and update other bundles, as well as enumerating the bundles and their service usage. This API has been used by many management agents to control OSGi frameworks, examples are as diverse as the Knopflerfish desktop and the Bosch OSGi management system.
The OSGi specification process requires an implementation for each specification and this has to be available under an open source license. There have also always been commercial implementations as well as other open source implementations. Currently, there are four open source implementations of the framework (Apache Felix, Eclipse Equinox, and Knopflerfish) and way too many to count implementations of OSGi services. The OSGi Specifications Implementation wikipedia page provides a good overview of many of these. Its great to see more and more commercial and open source projects delivering their projects and products as OSGi bundles. We are pleased to assist anyone who is thinking of doing this and you can ask for help on the OSGi users list.