Building a modern Java app with Eclipse, Maven, OSGi, and Spring DM (Part 3)

August 4th, 2009  |  Published in development

Background

As I mentioned a few weeks ago, we have about 120 modules (bundles) that are part of our regular build process.  Almost all of those bundles use Spring and Spring Dynamic Modules to wire their dependencies and configuration together.

Spring is used to automatically inject configuration settings into components (in conjunction with Apache Commons Configuration) as well as to inject the particular implementation into components where multiple options exist (such as database persistence, caching layers, etc.).

Many of the major components in the system are exposed as OSGi services and so we use Spring DM to automatically register and consume those services without coupling our code directly to OSGi.  Spring DM is invaluable at helping to “damp the use of services” (as SpringSource’s Glyn Normington put it at one point), meaning that by giving you a dynamic proxy instead of a reference to the real service, your app can better tolerate the perturbations caused by services coming and going.

I don’t have much else to describe about unique work we’re doing with Spring DM because the reference manual is so comprehensive!

Recommendations

  1. Keep your code decoupled from OSGi by relying on Spring DM as the glue between your app and the OSGi framework.
  2. As suggested by the Spring DM documentation, split your application contexts into two files: one to contain the standard Spring bean definitions, and a second to contain the OSGi specific beans.  This will make it easier to test and substitute mocks in place of real OSGi services.
  3. Create a parallel set of “test” contexts in the test hierarchy Maven has (src/test/resources) that mock certain objects or use.
  4. Create Spring integration tests to verify that your application contexts are correct (in particular, see the Spring TestContext Framework section of the reference).  Utilize OSGi integration tests in addition to confirm that your services and service references are defined correctly.

Tags: , ,

Building a modern Java app with Eclipse, Maven, OSGi, and Spring DM (Part 1)

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

  1. 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.
  2. 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.
  3. Take advantage of the dependencyManagement and pluginManagement elements so artifact versions don’t need to be specified in child POMs.
  4. 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.
  5. 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.
  6. 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: , , , ,