We mention OSGi at Bug Labs so often that sometimes we forget that a lot of people have never heard of OSGi or used OSGi before hacking on the BUG. I certainly hadn't when I came to Bug Labs a year and a half ago. Today I want to post a high level outline of what OSGi is and why it is so important to the BUG. I won't cover writing an application with OSGi in this post - only the concepts. I'm also going to skip over lots of sweet features in OSGi that we don't use because they are not applicable to our system.
Our Four Major Problems:
We have a "lifecycle" problem. If I have a program that needs to know its position and needs to display that to a screen, how does the app know when the GPS and the LCD module are plugged in? How does it know that the GPS just got pulled out? Some applications can run with absolutely no modules plugged in. It would be quite tiresome if every application had to re-implement the logic to check for these problems (not to mention horribly inefficient).
If we are going to have modular hardware, we must have modular software. Lets say I write an application that controls a motor via the Von Hippel module. One minute I want to have it controlled by tilting the BUG with the accelerometer. The next minute I want to pull out the accelerometer and plug in an LCD and want to control it using a GUI app. Imagine we had solved the life cycle problem without OSGi but had no modularity. The application using the accelerometer would be separate from the one using the LCD. Every time I made a change to one program's code I'd have to duplicate it on the other. Every time I changed modules the state of the controller would be lost. Instead we need a way to split the programs into pieces so that the controller is separate from the interface. Modularity of software is just as important as sleek plastic interfaces.
How do we share modules? If I have ten applications using your position they can't all keep control the GPS module independently. Sure you can poll its position when you need to, but one of the applications needs to initialize the GPS - what if one application sends it a series of commands to initialize while another is using it?
Finally, how do we write applications independent of hardware? If you write an app that requires position information it doesn't matter if that information comes from GPS, dead reckoning, GSM triangulation, Loran, a local positioning system, or a file loaded into an emulator. What matters is the logic in your code that handles that position data. We need to abstract away from simply calling a known module for its position.
OSGi - One solution:
This article should not suggest that OSGi is the one solution to rule them all. In fact on the BUG we have two solutions for modularity and sharing modules (web services - which are a subject for another post).
OSGi is a "dynamic module system". Dynamic in this context meaning that it loads and unloads modules during runtime.
The building blocks of an OSGi system are Bundles, Services, and the overhead Framework.
A Bundle is the basic unit of code in OSGi. Generally a few packages that have a set of dependencies, and when those dependencies are filled the Bundle is activated. When the dependencies are broken (say a module is pulled out) then the Bundle is stopped. On the BUG we generally think of Bundles as applications, but a lot of the underlying system is in Bundles that themselves are not quite full applications.
A Service is a message between different Bundles. Continuing the GPS example, the framework will allow your GPS application to run if a certain service (IPositionProvider on the BUG) is available. When you need position data you will call from your handle on the service the function getLatitudeLongitude(). OSGi will then call up the service and return a position, hiding the supplier from you. You don't need to know what is the service provider, simply that there is a provider that implements the functions you need to use. It does not have to be tied to hardware either, one application can provide services for another with no hardware interaction at all (in fact, this is how most OSGi is used outside of BUG). Data is simply passed back and forther between service provider and consumer, and everybody is happy.
The framework is a magical entity with omnipotent powers. It knows what every Bundle needs and which Bundles provide what Services. To be honest, I don't really know how this happens and it doesn't really matter. What matters to you is that there is a Service Registry, and it keeps track of all the Bundles and Services you will have to interface with and hides that complexity from you the application writer.
If you want to know more about how OSGi works, check out the OSGi alliance white papers.
The most important distinction between OSGi and other technologies like CORBA and Ice that seem to provide similiar functionality is that OSGi is a distinctly JVM technology. It runs entirely within a single JVM process, and only works with JVM languages. There is something called R-OSGi that spans multiple JVM processes which works on the BUG, but it is not the primary method of OSGi use. The advantage of this approach is that the framework is
very lightweight. It only marginally slows down communicating between service providers as compared to a middleware framework with things like XML message parsing or network communication latency. It is all contained on a single piece of hardware talking with no measurable latency. The downside is that it only works easily with Java & other JVM languages. We get around this by offering a parrallel system of web services to open access across networks and to other languages, but again that is the subject of another post.
In the end OSGi is quite an elegant framework for our system. There is a bit of boilerplate code to register what services your Bundle needs or provides. There is also a bit of planning your application to decide where to break into different bundles for modularity. But in the end we have a system where application developers do not have to worry much about making sure dependencies are met, or that an application could continue running after hardware is removed, or about what hardware (or software) implements the services they are calling. Don't reimplement code other people have written, just depend on their services. Simply write your application and the Bug drivers & OSGi will handle all the messy details.
JT: Do you think that in most open source projects you're going to get this eventual, almost like sheep farmers and cattle farmers, thing between the people who want higher performance and the people who want more features?
BA: There's always an inevitable -- no open source project is complete until it has a mail reader built-in, I think was the old joke. So there is always an issue of pushing in more features and so forth. I think that inevitably leads to some trees that solve this problem; some trees to solve that problem. In my own world, that was one of the reasons why when we started refactoring MySQL, we decided to go with the microkernel architecture so that we could actually take any form or any piece of -- you don't want any kind of authentication? Fine. We won't load an authentication module. You need PAM authentication? Fine. We'll load a PAM authentication module. So to me, that's kind of the natural evolution of open source--actually towards more microkernel-type architectures. Certain things I've seen recently about refactoring about Open Office and other projects, I think, kind of shows that long-term open source projects that tend to pick or tend to evolve eventually towards being a much more modular design tend to actually last longer.