Skip to content

3.3. OSGi best practices

Nelson Antunes edited this page Jan 23, 2019 · 8 revisions

Only export API packages and not implementation

Packages from API and Implementation artifacts must follow the coding guideline naming conventions.

Currently they state that API packages do not include any special “tag” such as api and that implementation packages should include impl in the package name.

The presence of the impl token has pratical consequences, as the maven-bundle-plugin will, with its default configuration, not export packages that include an impl or internal token. This export metadata is included in the manifest file.

Provide your implementations by sharing services, using the OSGi service registry. Along with the Have separate artifacts for API and Implementation best practice, this achieves a loose coupling of client, API and implementation.

Do not have split packages

When developing OSGi bundles a best practice is to keep all of the classes from any one package in a single bundle. A split package occurs when a package is exported by two bundles at the same version and the set of classes provided by each bundle differs. Classes might be duplicated or, more typically, part of the package is in one bundle and the remainder of the package is in another.

At run time, OSGi will satisfy a bundle's dependency specified by an Import-Package header with an export from another bundle. Even if there is more than one bundle that exports the package, only one of them is used to satisfy the dependency.

For example consider a bundle X that exports package my.amazing.package that has class my.amazing.package.A and a bundle Y that also exports package my.amazing.package that has class my.amazing.package.B.

However, a package import is always satisfied by exactly one other bundle. This means that a bundle Z that imports package my.amazing.package will either get class my.amazing.package.A or my.amazing.package.B but never both classes.

The only workaround is by violating the Do not use the Require-Bundle header best practice, specifying the bundle symbolic names of all bundles exporting the required package, tightening the coupling between the bundles. Not worth it.

Do not override maven-bundle-plugin configuration

We’re using the Maven bundle plug-in for automating the generation of bundle Manifests in a Maven project. As a good rule of thumb, we should rely on its default settings that automatically analyses and determines what packages to export or import.

For instance, when we don’t specify any Export-Package instruction, the default behaviour of the Maven bundle plug-in is to exclude any packages that contain a path segment equal to impl or internal, facilitating complying with the Only export API packages and not implementation best practice.

There are some cases where our build tool stack is unable to infer the proper import packages. For example the usage of the <pen:di> tag in a blueprint descriptor requires the import of the org.pentaho.di.osgi package in order to be properly processed.

This import should be explicitly added in the configuration of the maven-bundle-plugin. Remember to add * at the end of the import configuration so that the rest of the imports are automatically determined.

<configuration>
  <instructions>
    <Import-Package>org.pentaho.di.osgi,*</Import-Package>
  </instructions>
</configuration>

Avoid the optional resolution when specifying an Import-Packages. Typically, this can be better implemented by creating separate bundles for separate features.

Having optional resolution on import-packages defeat the OSGi advantage of knowing that we have all necessary definitions during bundle activation time instead of runtime.

EE build considerations

If the project is CE, the maven project packaging should be set to bundle.

In EE bundles we actually need to set the packaging to jar because of obfuscation. In these case the generation of the OSGi metadata is done by a custom execution of the maven-bundle-plugin.

Do not use the Require-Bundle header

The Require-Bundle header creates a tight coupling between the requiring bundle and the required bundle, which is an implicit dependency towards an implementation rather than an interface. Thus, it impacts the flexibility of dependency resolution, as the resolver has only one source to provide the dependency.

This also naturally complicates refactoring activities: moving a package from one bundle to the other requires to patch all bundles depending on it to point to the new bundle. In contrast, the Import-Package header only relies on an interface and various bundles may offer the corresponding package.

Finally, Require-Bundle automatically imports all the exported packages of the required bundle, which may introduce unnecessary dependencies.

from https://hal.archives-ouvertes.fr/hal-01740131/document

Importing at the granularity of packages, on the other hand, allows the resolver to be more flexible, because there are fewer constraints: if a package is already available and resolved from another bundle, the resolver could use that package instead.

Do not use the Dynamic-Import-Package header

This header lists a set of packages that may be imported at runtime after the bundle has reached a resolved state. In this case, dependency resolution failures may appear in later stages in the life cycle of the system and are harder to diagnose. This effectively hurts the fail fast idiom adopted by the OSGi framework.

Also, the DynamicImport-Package creates an overhead due to the need to dynamically resolve packages every time a dynamic class is used.

from https://hal.archives-ouvertes.fr/hal-01740131/document

Dynamic imports are similar to optional packages but differ in the fact that they are handled after the bundle is resolved.