In the past two years I have given numerous conference talks about developing with OSGi. One of my favorite talks is one where I show OSGi development by building a complete application from scratch during the talk. People are mostly impressed by the fact that OSGi is a lot easier to use than they expect. Besides that it's easy, it also has some great benefits such as:
The following video shows the difference between required and optional dependencies.
Required services don't require any extra code; there is no code to handle the case when a dependency is not available. This makes using services as easy as using a static dependency injection model such as CDI or Spring.
Optional dependencies
When declaring an optional dependency you accept the fact that it might not always be there. This is useful when a component can still do useful work when a dependency is unavailable. An often used example is a dependency on LogService. When LogService is available you want to use it, but without logging a component can still work.
By default, Apache Felix Dependency Manager injects a null object when a dependency becomes unavailable. A null object returns null on every method invocation. Void methods become basically no-ops. This also allows us to write code to handle the fact that a dependency is unavailable. For example, we could fall back on other code paths, or show users specific error messages.
Choosing between required vs optional
So when to use required and when to use optional dependencies? My own rule of thumb is to use required dependencies by default, unless there is really something useful to do when a dependency is unavailable.
Ever tried updating a configuration file in a running Java EE application? This hardly ever works, because configuration is often used to bootstrap static components. Once these components are bootstrapped, they will not re-initialize until the application is restarted. With OSGi services this is trivial to do. A component can be configured using Configuration Admin, and whenever the configuration changes, the component is updated.
Service dynamics do add some complexity, but using a dependency injection framework this is almost entirely taken care of by the framework. It does give some really nice benefits both during development and production in return. Once you have gotten used to the fast code-save-test cycle in Bndtools it's hard to imagine going back to slow redeployments.
- True modularity, which is key to maintainable code
- An extremely fast code-save-test cycle in the IDE
- Incremental deployments (using Apache ACE)
- Service dynamics
That last point might look a bit strange, but it's there for a reason. During each and every talk someone will ask something like:
"Those dynamic services look powerful, but it also adds complexity. Why do we need services to be dynamic?"
This is a valid question. The fact that services can come and go at any given time adds complexity because you will have to deal with the possibility that dependencies are unavailable somehow. The answer is a bit complicated, because there are several reasons why dynamics are well worth the extra complexity. But before we get into that, lets take a look at how difficult (or easy) it really is to use them.
Working with service dynamics
An OSGi service can be registered and deregistered at any given time. Practically this means that you never know if another service that you depend on will still be there the next moment. Depending on the dependency injection model that you are using (e.g. Apache Felix Dependency Manager, Declarative Services, iPojo etc.) there are a number of ways to deal with this fact. As an example I will focus on the two most often used mechanisms in Apache Felix Dependency Manager.
Required dependencies
The easiest way to deal with dynamics is to declare required dependencies. By using a required dependency the component doesn't have to deal with the situation where dependencies are unavailable. Instead it will be deregistered itself, until all it's required dependencies are available again. The downside of this approach is that deregistering a component can have ripple effect through the system, where potentially all services will become unavailable. But when no useful work can be done when that component is available, it's the right thing to do.
The following video shows the difference between required and optional dependencies.
Required services don't require any extra code; there is no code to handle the case when a dependency is not available. This makes using services as easy as using a static dependency injection model such as CDI or Spring.
Optional dependencies
When declaring an optional dependency you accept the fact that it might not always be there. This is useful when a component can still do useful work when a dependency is unavailable. An often used example is a dependency on LogService. When LogService is available you want to use it, but without logging a component can still work.
By default, Apache Felix Dependency Manager injects a null object when a dependency becomes unavailable. A null object returns null on every method invocation. Void methods become basically no-ops. This also allows us to write code to handle the fact that a dependency is unavailable. For example, we could fall back on other code paths, or show users specific error messages.
Choosing between required vs optional
So when to use required and when to use optional dependencies? My own rule of thumb is to use required dependencies by default, unless there is really something useful to do when a dependency is unavailable.
Reasons for dynamic services
Working with dynamic services is hardly any more work than using static dependency injection. But the question remains, why do you need it? Let's look at a few benefits.
Hot code deployment during development
Have you ever envied dynamic language users for their fast code-save-test development cycle? In most Java environments the experience is a lot slower; compile code, create an archive and re-install the complete application in an application server. In modern application servers this might costs only a few seconds, but it's still extremely disturbing to the development experience. The problem is that the application server needs to re-install the full application, it's not possible to just re-install the pieces that you actually changed.
OSGi is designed to deal with updates to bundles in a running framework. Bndtools uses this to re-install updated bundles as soon as you save your code. This process is so fast that you don't actually notice any delay between saving a class and seeing it's changes in the running application. The video below demonstrates this. This feature alone would justify using OSGi and Bndtools :-)
You might wonder how this is related to dynamic services. Reloading part of an application during runtime is only possible if it's internals can deal with parts of the application (temporarily) not being available, and services offer exactly that.
Hot code deployment in production
The same dynamic update mechanism can be used during production updates. A production server doesn't have to be stopped completely to apply a hotfix. When using a provisioning server such as Apache ACE we can even push updates to large amounts of servers or devices.
Note that there is actually some downtime during the update. The updated bundle must be re-installed, and although this takes less than a second, it's services are unavailable during the update. Depending on your situation this might or might not be acceptable. Of course you can use a cluster or load balancer to deal with failover during updates as well. Even than, it's great if deploying a hotfix only takes a second.
Creating services dynamically
So far we have been talking about services that become temporarily unavailable because of bundle updates. Services can be created from code during runtime as well. An often used scenario is creating new services when the environment changes. For example, new screens might be added to a user interface when new configuration data is found in the database. Of course you could create some custom dynamic registration mechanism for this, but it's a lot easier when you can just build on basic building blocks of the runtime instead of re-inventing the wheel.
Configuration updates
OSGi is designed to deal with updates to bundles in a running framework. Bndtools uses this to re-install updated bundles as soon as you save your code. This process is so fast that you don't actually notice any delay between saving a class and seeing it's changes in the running application. The video below demonstrates this. This feature alone would justify using OSGi and Bndtools :-)
You might wonder how this is related to dynamic services. Reloading part of an application during runtime is only possible if it's internals can deal with parts of the application (temporarily) not being available, and services offer exactly that.
Hot code deployment in production
The same dynamic update mechanism can be used during production updates. A production server doesn't have to be stopped completely to apply a hotfix. When using a provisioning server such as Apache ACE we can even push updates to large amounts of servers or devices.
Note that there is actually some downtime during the update. The updated bundle must be re-installed, and although this takes less than a second, it's services are unavailable during the update. Depending on your situation this might or might not be acceptable. Of course you can use a cluster or load balancer to deal with failover during updates as well. Even than, it's great if deploying a hotfix only takes a second.
Creating services dynamically
So far we have been talking about services that become temporarily unavailable because of bundle updates. Services can be created from code during runtime as well. An often used scenario is creating new services when the environment changes. For example, new screens might be added to a user interface when new configuration data is found in the database. Of course you could create some custom dynamic registration mechanism for this, but it's a lot easier when you can just build on basic building blocks of the runtime instead of re-inventing the wheel.
Configuration updates
Service dynamics do add some complexity, but using a dependency injection framework this is almost entirely taken care of by the framework. It does give some really nice benefits both during development and production in return. Once you have gotten used to the fast code-save-test cycle in Bndtools it's hard to imagine going back to slow redeployments.