Using Maven to manage cross-team code sharing

by
Enrique Lara, Senior Software Engineer
Object Computing, Inc. (OCI)

Introduction

Apache Maven is a "software project management and comprehension tool". It uses commands that may be orchestrated into a flow to perform actions against a software project.

In this article we will use Maven to create, build, test, and share Java code. Our setting is within com.company. We will start in deptX, which is developing and distributing the Engine project. We will follow the code over to where it is used by deptY for the Car project.

If you are unfamiliar with Maven, you may want read through Maven in five minutes.

Terminology

goal
A specific task or command that acts upon the available project assets.
phase
A particular step in the lifecycle state machine. Goals may be bound to a particular phase.
lifecycle
A lifecycle represents a sequence of phases tied togethor to represent a SDLC objective.
plugin
A plugin is a project that provides one or more goal implementations.
repository
A repository holds build artifacts and dependencies (jar, war, ear).
pom
A pom is a configuration file that declares project resources, identifier information, and holds plugin customization and configuration.

Required Tools

The following tools are required in order to build the example projects:

Java Development Kit
Version 1.6.0_17 was used in the development of this article, but any newer version should work as well.
Apache Maven
Version 3.0-alpha-6 was used in the development of this article.

The Engine Project

The Engine Project is a new project for deptX. It is being developed by user1.

Create

Execute the following Maven goal:

% cd ~/proj
% mvn archetype:create -DgroupId=com.company.deptx \
          -DartifactId=engine-factory -Dversion=1.0

The above specifies to Maven that we want to run the create goal of the archetype plugin. Maven downloads any missing dependencies necessary for the plugin to run successfully, and then kicks off the specified goal. This particular goal creates a minimalist project that includes a pom.xml, a JUnit test, and a Java file, using the standard directory layout.

The groupId could be considered the project's namespace and the artifactId the unique identifier within that namespace. We are foregoing the generation of a SNAPSHOT version for the purposes of this example.

Code

Let's test and implement something to share:

engine-factory/src/test/java/com/company/deptx/engine/EngineFactoryTest.java
package com.company.deptx.engine;

import junit.framework.Test;
import junit.framework.TestCase;
import junit.framework.TestSuite;

public class EngineFactoryTest extends TestCase {
    public void testEngineFactory() {
        assertEquals(13, EngineFactory.
                     getEngine("GAS").getMpg());
        assertEquals(42, EngineFactory.
                     getEngine("DIESEL").getMpg());
        assertNull(EngineFactory.getEngine("RADIUM"));
    }
    
    public static Test suite() {
        return new TestSuite( EngineFactoryTest.class );
    }
    
    public static void main(String[] args) {
        org.junit.runner.JUnitCore.
            runClasses(new Class[] { EngineFactoryTest.class });
    }
}
engine-factory/src/main/java/com/company/deptx/engine/Engine.java
package com.company.deptx.engine;

public interface Engine {
    int getMpg();
}
engine-factory/src/main/java/com/company/deptx/engine/DieselEngine.java
package com.company.deptx.engine;

public class DieselEngine implements Engine {
    public int getMpg() { return 42; }
}
engine-factory/src/main/java/com/company/deptx/engine/GasEngine.java
package com.company.deptx.engine;

public class GasEngine implements Engine {
    public int getMpg() { return 13; }
}
engine-factory/src/main/java/com/company/deptx/engine/EngineFactory.java
package com.company.deptx.engine;
public class EngineFactory {
    public static Engine getEngine(String fuel) {
        if (fuel.equals("DIESEL")) {
            return new DieselEngine();  
        } else if(fuel.equals("GAS")) {
            return new GasEngine();
        } else {
            return null;
        }
    }
}

Build

Let's build this engine project.

% cd ~/proj/engine-factory
% mvn package

We should now have a jar in the project's output directory:

% ls target/*jar
target/engine-factory-1.0.jar

Share

The default lifecycle already offers us a couple of phases to simplify sharing our artifacts. The "install" and "deploy" phases.

No small amount of confusion arises from the naming of the phases "install" and "deploy". By default, these phases are only going to take the jar and copy it into a Maven repository. Maven isn't going to auto-magically know how to build an .msi file and then in turn run that so it is installed. On a similar tack, without further customizing the pom, Maven won't know to bundle the jar and then deploy it into your app server or what have you.

Let's try to share via install.

The install:install goal is bound to the install phase. This goal will copy the jar into the user's local Maven repository.

% mvn install
% ls ~/.m2/repository/com/company/deptx/engine-factory/1.0/*.jar
~/.m2/repository/com/company/deptx/engine-factory/1.0/engine-factory-1.0
.jar

This is great for using the jar in other local projects that this particular user is working on, but doesn't quite get us to "1.0".

To get there, we need to make our jar available to our (future) users. Outside the mechanisms of Maven, this could be accomplished in any number of ways: checking the jar into version control, manually copying the file into a shared volume, or even sending the jar via email.

The last phase in the default lifecycle is the deploy phase, so let's aim towards that.

Bound to this phase is the deploy:deploy goal, which offers a few different transfer mechanisms, including file-system copy, ssh, and ftp.

As more jars get both consumed and deployed by deptX, a repository management tool might start to look appealing. For this example, let's simply create a repository on the file-system. In the spirit of sharing, let's imagine that this file-system path is available to our future users.

% mkdir -p /mvn/deptx

Let's configure the engine-factory project to point to this location for distribution:

engine-factory/pom.xml
...
  <distributionManagement>
    <repository>
      <id>deptx-repository</id>
      <url>file:///mvn/deptx</url>
    </repository>
  </distributionManagement>  
...

Execute the following Maven goal to deploy:

% mvn deploy

In our simple example, user1 can close the books on this version with a simple email:

% mail -s "Engine went 1.0"  user2@depty.company.com << EOF
Hi User2, 
   Here are the coordinates:
  com.company.deptx:engine-factory:1.0

--
user1@deptx.company.com
EOF

The Car Project

The Car Project has been under development by deptY for some time. The release of engine-factory:1.0 means user2 can implement those features that require an engine.

Create

To follow along as user2, let's create a new project that will depend on the Engine library.

% cd ~/proj
% mvn archetype:create -DgroupId=com.company.depty -DartifactId=car

Use

To use the engine-factory, we include it as a dependency in the pom:

car/pom.xml
...
  <dependencies>
    <dependency>
      <groupId>com.company.deptx</groupId>
      <artifactId>engine-factory</artifactId>
      <version>1.0</version>    
    </dependency>
...

If we tried building (compile, test, package, etc), Maven would fail with something like:

...
[INFO] Failed to resolve artifact.

Missing:
----------
1) com.company.deptx:engine-factory:jar:1.0

  Try downloading the file manually from the project website.

  Then, install it using the command: 
      mvn install:install-file -DgroupId=com.company.deptx  \
-DartifactId=engine-factory -Dversion=1.0 -Dpackaging=jar \
-Dfile=/path/to/file

...

We have to configure the car project so the Maven dependency resolution machinery knows to look in the deptX repository for the engine-factory jar.

We can do this in the pom:

car/pom.xml
...
  <repositories>
    <repository>
      <id>deptx-repository</id>
      <url>file:///mvn/deptx</url>      
    </repository>
  </repositories>
...

Some Java code:

car/src/test/java/com/company/depty/car/CarTest.java
package com.company.depty.car;

import com.company.deptx.engine.*;

import junit.framework.Test;
import junit.framework.TestCase;
import junit.framework.TestSuite;

public class CarTest extends TestCase {
    public void testCarGo() {
        Engine engine = EngineFactory.getEngine("DIESEL");
        Car car = new Car(engine);
        car.go();
    }
    
    public static Test suite() {
        return new TestSuite( CarTest.class );
    }
    
    public static void main(String[] args) {
        org.junit.runner.JUnitCore.
            runClasses(new Class[] { CarTest.class });
    }
}
car/src/main/java/com/company/depty/car/Car.java
package com.company.depty.car;

import com.company.deptx.engine.Engine;

public class Car  {
    private final Engine engine;
    public Car(final Engine engine) {
        this.engine = engine;
    }
    
    public void go() {
        System.out.println("VROOM");
    }
}

Build

Let's "aim high" and specify the install phase:

% cd ~/proj/car
% mvn install

Summary

This article focused on how Apache Maven supports sharing and reusing jars across teams/departments/projects - Maven provides far more. An invaluable resource is Maven: The Definitive Guide, which has been split into Maven by Example and Maven: The Complete Reference.

References

See Also


Valid XHTML 1.0 Strict [Valid RSS]
RSS
Top