July 9th, 2009 |
Published in
development
Continuing from yesterday’s post about Maven, I’m going to briefly discuss our approach to handling OSGi.
Every module that we build, whether it’s a JAR or a WAR, is packaged as an OSGi bundle. Originally we relied on the maven-bundle-plugin from Apache Felix to generate the MANIFEST.MF files every time the package phase was executed. As the manifest was dynamically generated, we were careful to never check in the generated manifest, as it would result in errors when a developer or our continuous integration system attempted to build the product and would inadvertently clobber the read-only manifest file.
As part of the plug-in configuration we originally hand-coded each and every package import, although recently we’ve been relying more on bnd’s ability to scan bytecode and only adding imports for packages referenced from Spring application contexts or other non-bytecode sources. This has made the process much easier, but there is still a chance that you could end up with a ClassNotFoundException or NoClassDefFoundError if you’re not careful (more on that in a moment).
We have also recently switched to generating the manifest file once and then checking it into Perforce. The static manifest means that when we import the modules into Eclipse we can take advantage of the PDE tooling to edit and maintain the manifest. It also allows us to have the modules (bundles) automatically added to the Eclipse target platform, making it easy to run the product and debug it from within the Eclipse IDE.
One downside to these approaches is that there is still duplicated metadata between Maven and the maven-bundle-plugin/OSGi because they do not share a common metadata source. Our practice of relying on bnd to pick up most imports has minimized this to some degree. We expect that future improvements to Maven, PDE, and other OSGi tooling will eliminate the problem entirely.
Testing
OSGi metadata is challenging to get right the first time, so early on we established the practice of creating OSGi integration tests for each bundle that was produced. The purpose of the tests is to verify that the correct packages are being imported/exported from a bundle, that no implementation classes slip into the export list, and that any services or service references are correctly registered/resolved. One of my colleagues wrote an abstract class that takes away much of the pain of programmaticaly starting Eclipse Equinox and automatically loading in our third-party dependencies, so all the individual test authors needs to do is essentially reproduce the OSGi metadata/contract in JUnit form (we’re currently using Spring DM’s OSGi tests support).
So far the tests have been rather successful at identifying missing dependencies prior to deployment. The only real downside to this approach is the duplication of metadata in the test cases.
Recommendations
- Rely on bnd’s bytecode scanning technique to pick up most package imports instead of explicitly adding them to the maven-bundle-plugin’s configuration.
- Explicitly add packages referenced from XML files (Spring, Hibernate, etc.) and for classes that are dynamically loaded. Hibernate or other frameworks that use cglib/javassist can be particularly difficult to get right if you’re not extremely careful.
- Make sure your modules always have a manifest file with OSGi metadata so you can take advantage of the Eclipse PDE tooling.
- Run your bundles in an environment as similar as possible to your target platform prior to deploying in production so you can ensure that all the necessary dependencies have been specified and are present in the environment.
Tags: eclipse, java, maven, osgi
July 8th, 2009 |
Published in
development
Last week Michael Nygard tweeted about difficulties with Eclipse, Maven, OSGi, and Spring DM. Given that Michael and others have expressed interest in hearing how we’ve been using all of those technologies when developing VMware’s forthcoming vCloud product, I thought I’d try to go through it over the next week or two in a series of blog posts. Today’s post will provide some of the details about our base Maven setup.
Note: I won’t be talking about the specifics of our product, so if you’d like more details please consult the presentation from my colleague Orran Krieger. I’m also not going to touch much on our deployment work; for that see my other colleague Stephen Evanchik’s blog or the Eclipse Integration for Karaf project he started on FUSE Forge.
Project layout and building the product
The product codebase is almost entirely Java and when we first started writing code last year it seemed to make sense to use a tool that understood Java and was able to help us resolve third-party dependencies. At the time we weren’t really aware of Ant+Ivy, so we opted to go with Maven. It was also nice that Maven follows the convention-over-configuration approach which made it very easy to create new modules and quickly get new developers up to speed. One downside at the time was that the Sonatype folks were still working on their Maven book, so we ended up having to figure out a number of Maven best practices on our own, which resulted in some frustration early on until we got over the learning curve. Today we have about 120 modules that are part of the build, and 1-2 dozen additional modules that are not part of the regular process.
We have a single master POM that defines the project defaults (including artifact versions and plug-in configurations). The rest of the modules are organized into subsystems, and each subsystem has its own POM to allow us to build them in isolation if we wish (in some cases there are inter-subsystem dependencies that prevent us from doing so). For the final deliverable we rely on Maven assemblies to collect the appropriate artifacts and package them into a tarball suitable for distribution.
One other important distinction is that we always download artifacts into a local repository that is checked into Perforce (the SCM system we use) and run Maven in offline mode. This allows us to reproduce any build based on the Perforce changelist number and also means the team doesn’t spend all day downloading artifacts just to do a build.
Recommendations
- Place parent POMs in a sibling directory (i.e. ../foo-parent) instead of in the parent directory (../) as Eclipse/m2eclipse seems to handle the nested projects slightly better. We originally ran into problems where Eclipse would start shifting files and output artifacts around when the parent POM wasn’t in its own directory.
- Define variables in the master POM to capture artifact versions. This will allow you to update the value in one place and have the change automatically propagate throughout the system. There is nothing more frustrating than having to search and replace version strings through 120 modules and in different scopes. Multiply that by the number of artifacts that comprise Spring or Spring DM and you’ll soon be begging for a drink.
- Take advantage of the dependencyManagement and pluginManagement elements so artifact versions don’t need to be specified in child POMs.
- Utilize profiles to pull out processes that don’t need to be executed all of the time. We originally generated some JAXB and WSDL stubs every time until we eventually moved those goals into deactivated profiles for the few times they actually needed to be changed. We also started to do this for the MANIFEST.MF files, which I’ll touch on more in a future post.
- Don’t use snapshot versions of artifacts unless absolutely necessary. They make it extremely difficult (if not impossible) to produce repeatable builds. We got in the habit of disabling snapshots in our repository definitions to make sure they didn’t slip in.
- Don’t use version ranges for dependencies if possible; you want to be able to recreate a build exactly without having to guess what version of an artifact was pulled from the repository at the time of the original build. If you’re using an offline repository could be slightly easier because you have a static snapshot of the repository and can sync to a particular changelist number (referring to Perforce in particular).
Tags: eclipse, java, maven, osgi, spring-dm