Fine granularity in Bundles

Author: Niclas Hedhman, 2006. Th whole article and code snippets are licensed under the Apache License ver 2.0

Work in progress

This is work in progress and not completed yet.

Fine-grained vs Coarse-grained bundles

Fine-grained granularity means that very small pieces of functionality is put in a OSGi bundle and that each bundle does not necessary provide anything useful by itself. Instead a group of bundles coooperate together to fulfill the actual functionality of the user (typically a programmer or a deployer).

Coarse-grainde granularity means that very complete sets of functionality is put into a bundle and by deploying such a bundle one gets an awful lot of functionality in one go.

Why should one choose a fine-grained approach?

The fine-grained approach leads to an enormous amount of bundles that must be managed (tracked, versioned, deployed, et cetera) and this puts strain on the development team. It is difficult to get a system up and running manually.

The coarse-grained approach often makes the system too heavy/large, too inflexible and harder to do hot-reloads and fault resilient systems. One can not upgrade a small defective part of the system, which could be reloaded in a matter of milli-seconds, and instead need to do large and potentially expensive maintenance upgrades and restarts.

Another advantage of the fine-grained granularity approach is that re-use will improve. The larger and more monolithic a system is, the lesser the chance it can be re-used without code changes. In a coarse-grained system, we need to do source code changes, whereas in a fine-grained system, we can keep the binaries of the parts that performs what we need in the new system, and only replace the bundles that are different.

So, the main disadvantage of the fine-grained approach is that it becomes difficult to develop and get the system deployed, whereas the coarse-grained approach leads to a "lesser" running system and harder re-use between products/projects. The problem with the fine-grained system should be possible to solve with better tools for development and deployment instead of the creation of coarse-grained, lesser systems.

What does a fine-grained system mean?

The pattern that starts to emerge here at OPS4J is driven by the desire to allow the implementation to be replaced in runtime, without stopping all the bundles that has this dependency. By utilizing the Service layer in OSGi, plus packaging of the bundles according to the explaination below, we can make this a reality. It can be seen in Pax Logging which allows the logging service implementation to be replaced in runtime, without stopping all the bundles that uses it.

Do ONE thing and do it WELL.

If you feel that the subsystem you are working on has more than one functionality in it, then it probably has and should be broken apart. Try to abstract the essence of a functionality into a well-defined contract, express that in interfaces with the semantics clarified.

Example

Let's look into an hypothetical system.

Problem description

A system that monitors the water levels in tanks, and if those levels are too high or too low, then set the system in an alarmed state and notify relevant personnel.

First step - Break it apart

The first thing we should notice is that there are quite a lot of functionality in such a short sentence, and it may not be a good choice to stuff all of that in a bundle;

  • Measurement of levels -> I/O system.
  • Detection of the level above and below certain limits. -> Business Logic
  • Management of an Alarm state. -> Alarm system
  • Notification of events (alarm events) to humans. -> Notification system

Each of these systems should probably be developed as separate subsystems. Let's take a closer look at the Alarm System

Second step - Define the abstraction

Alarm systems exist in the process control and industrial automation world as a kind of simple statemachine which are triggered by various conditions. Unfortunately, the industry has not been able to standardize how such statemachine should operate. Some are simpler, some are more complex, often depending on which vendor but some projects even require that different alarms has different alarm models.

So, we want an Alarm API which allows programmers to easily define Alarms and trigger those alarms when conditions occur. We want to be able to replace the alarm model (the statemachine) for each alarm and we want to be able to fix bugs in the implementation in runtime, without stopping all the bundles that uses alarms.

Bundles

API bundle

To be able to reload the implementation without stopping the client bundles, all classes that the clients use are located in a bundle that is not part of the reload. We need to put in;

  1. Service interface for the Alarm Service.
  2. Exceptions, return types and method argument types used.
  3. Proxy for the Alarm statemachine, which delegates to the actual implementation.

Implementation bundle

The implementation bundle will contain the Alarm Service implementation, but also the Alarm implementation, which will delegate to the Alarm SPI for the statemachine operations.

  1. Alarm Service implementation.
  2. Alarm implementation.

SPI bundle

We abstract the statemachine into a lower level API, here called the Service Provider Interface (SPI). The SPI contains the abstraction of the statemachine for the Alarm.

SPI extension bundles

Then we need bundles for each statemachine type, which provides the various supported alarm models.