This post is just a reflection, based on something that was recently discussed in the Bnd developer’s Slack group. It caused us to briefly touch on an age-old argument of Import-Package
vs Require-Bundle
.
Historically, the Bnd team has been staunch supporters of favouring Import-Package
over Require-Bundle
. However, the Eclipse team has not been so purist and the Eclipse core currently relies on Require-Bundle
in order to work properly. Even @pkriens admitted (in the aforementioned Bndtools Slack group) to softening his purist approach against Require-Bundle
(though I’m still not 100% sure if he was serious or joking… ) I was prompted to re-visit @pkriens blog post from a decade-and-a-half ago where the Import-Package
approach was put forward (OSGi Blog: JSR 277 and Import-Package).
I nearly always use Import-Package
for the reasons @pkriens outlined in the article. The only time I don’t is when my upstream dependencies are constructed in such a way as to force me to use Require-Bundle
, often because of split packages (which is the case with iDempiere). However, at the same time, I have also seen a glimmer of when Require-Bundle
might be a good idea (or at least, not a terrible idea).
In @pkriens’ original article, the argument is based on the concept of cohesion. Cohesion is roughly defined as the amount of interdependency between different parts of the code. If you draw a dependency graph, a highly cohesive block of code will have lots of links. To put it another way, there will lots of different parts of the code that don’t really work without each other (so it is rare to import one package without the others), and changes to one more often than not require changes to the others.
The argument consists of two parts:
- Versioning makes most sense on aggregates of code that are highly cohesive.
- Packages are usually highly cohesive, but bundles are usually not so cohesive.
I think that the first part of this argument is 100% accurate. However, upon reflection, I think that the second part of the argument, while generally true, is less absolute - and it is something that is under the control of the developer.
I think it is certainly true that packages tend to be more cohesive than bundles. I think it is equally certain that it is possible to produce packages that aren’t quite so cohesive - the fact that packages can be “split” across bundles, causing the “split package” problem, is an indication of a package whose cohesion is breaking up and should probably be refactored into separate packages.
However, it is possible to have a cohesive group that spans multiple packages. Examples are some of the API specs out there like JAX-WS or servlet APIs - groups of packages that implement a single version of the API. In such cases, it often makes sense to use a single version number for the group of public API packages. The OSGi even created the concept of “contracts” to encompass this very idea (disclaimer: I’ve never really used contracts in OSGi so this is based on my theoretical understanding). More on contracts here and here.
It is true that a bundle could theoretically contain packages that aren’t highly cohesive (indeed, in practice this is often the case). However, it is also possible that a developer could make a commitment to downstream users to be careful about how they aggregate (exported) packages into bundles - to maintain only and all cohesive packages in their bundle (ie, don’t move them out somewhere else without an appropriate major version bump, and don’t put other packages in there that aren’t closely related). If such a commitment is made, then the bundle becomes a de facto type of a “contract”. The advantage of using Require-Bundle
over a contract is that you don’t have to explicitly list all of the required packages that you will inevitably require if you are using the API.
The main disadvantage of Require-Bundle
is that you end up importing all packages in the bundle - including those that you don’t need. This increases dependency fanout unnecessarily. However, if the bundle you are requiring consists of only cohesive packages, chances are that you will end up needing to import all or most of them anyway, and as the packages are mutually cohesive they are likely to have more-or-less the same set of transitive dependencies so the chances of increasing your dependency fanout is small.
That being said, the only advantage I can think of here is that it’s easier to write your dependencies in your OSGi manifest - you only have to put one Require-Bundle
rather than several Import-Package
s. This advantage isn’t much of a real issue if you’re using Bnd to build your bundles (and automatically generate the appropriate Import-Package
statements).
I’m not sure what the runtime performance overheads are of Require-Bundle
vs Import-Package
though. I can’t imagine there would be a great deal of measurable difference between the two.
So in the end, I think sticking with Import-Package
is probably the best way to go if you’re using the Bnd toolchain. However perhaps Require-Bundle
is not (always) as bad as it seems?
Thoughts?