Tip: Split Packages and Visibility

Tip: Split Packages and Visibility

In an OSGi-based system, there are cases when you want to do some heavy refactoring but run the risk of breaking downstream clients. For example, let’s say you have plug-in A, it exports the com.company.util package. Now let’s pretend this package contains utility methods for math functions and logging. Now let’s say you have a request to break apart the math and logging code into separate plug-ins (B and C) but don’t want to break downstream clients that depend on you. How can we do this? There’s two steps, re-exporting bundles and splitting packages.

Re-exporting Bundles

The first step to ensure we don’t break downstream clients is to re-export the two new bundles B and C within A. In the Require-Bundle header, we would add the ‘visibility:=reexport’ attribute to B and C within bundle A’s MANIFEST.MF file.

Bundle-SymbolicName: a;
Require-Bundle: b;visibility:=reexport, c;visibility:=reexport

This re-exporting of dependencies allow downstream clients to still depend on ‘A’ and get the math and logging code that reside in new plug-ins. This also allows clients to depend on the math or logging code, depending on their needs, so everyone wins. For more information about the visibility attribute, check out the OSGi specification 3.13.1 section.

So this takes care of all of our Require-Bundle clients… what about the guys that use the Import-Package header?

Split Packages

So, what happens now to people that use to Import-Package: com.company.util… if you know OSGi well… they will just get wired to one of the producers of this package, instead of the desired result of including both bundles B and C which export that same package. How do we fix this problem so the OSGi resolver is aware that these packages should really be merged? The answer is split packages. This is what the MANIFEST.MF would look like for B and C:

Bundle-SymbolicName: b
Export-Package: com.company.util;b=split;mandatory:=b

Bundle-SymbolicName: c
Export-Package: com.company.util;c=split;mandatory:=c

Now the resolver is aware that when you import the ‘com.company.util’ package via a Require-Bundle dependency, you should be wired to B and C if you want the com.company.util package. However, split packages can be dangerous because they are open ended… how are you really sure that you’re really getting what was intended in a system? The OSGi specification talks about these dangers in section 3.13.2

For those reading this far… some of this may sound familiar to you as this is exactly what Eclipse did with its org.eclipse.core.runtime bundle. If you look at the org.eclipse.core.runtime bundle and the org.eclipse.equinox.common bundle, you’ll see the use of split packages and bundle re-exporting as a way to maintain backwards compatibility.

In the end, this shows the complexity of not breaking downstream clients when working in a modular system. There are always little things you have to be aware of when you refactor your code in a modular system like OSGi. Now it’s time to get back to the Olympics 😉