DevOps Zone is brought to you in partnership with:

Kief is a software delivery consultant with ThoughtWorks in London, specializing in tools, practices, and processes for the continuous delivery of software. Kief is a DZone MVB and is not an employee of DZone and has posted 19 posts at DZone. You can read more from them at their website. View Full User Profile

Maven: great idea, poor implementation (Part 2)

01.09.2012
| 10424 views |
  • submit to reddit

In my previous post I explained why Maven is a good concept for Java project builds. In this post I'll delve into a key area where it falls down, the overcomplexity of its configuration.

In brief, we have a proliferation of home-brewed build systems created in many different ways using Ant, all of which do much the same thing. Since the vast majority of Java projects have very similar build requirements, an off the shelf build system should be able to do the job.

More to the point, a standard build system using convention over configuration should be simple to set up and maintain. This in turn helps the development team's velocity, since less time is spent fiddling with (or worse, fighting against) the build system, so more time can be spent delivering value for customers.

Maven fulfils this if your build needs are ridiculously simple. Slap your source code into folders for application code and test code, name your tests right, and put in a very small pom.xml file. Run "mvn install" and your code is compiled, unit tested, and ready to deploy.

Things get ugly pretty quickly though, as soon as you need to make even a small tweak to the basic build.

For example, let's look at what's involved in changing the version of Java you want your project built to, for language compatibility and/or for compatibility with the runtime environment your project will be used in. Maven 3.0.x uses Java 1.5 by default, even if you have Java 1.6 installed, which is sensible enough.

So we have two parameters that need changing, language version for the source, and target version for generated classes. Here's what we have to add to our simple pom.xml to achieve this:

<project>
  [...]
  <build>
    [...]
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-compiler-plugin</artifactId>
        <version>2.3.2</version>
        <configuration>
          <source>1.4</source>
          <target>1.4</target>
        </configuration>
      </plugin>
    </plugins>
    [...]
  </build>
  [...]
</project>

So the first thing that strikes you is, holy spaghetti, that's a lot of scaffolding for two parameters. Someone wading through a pom file trying to understand our build in order to modify or fix it will have a hard time working out the meat of this snippet, and it's not clear that it's all only there for those two parameters.

Much worse, the extra scaffolding isn't harmless. Before we had this, compilation was done by the maven-compiler-plugin, using the latest available version for the version of Maven we're using. Now this out of the box default has been surfaced in the pom file. Our project is locked into knowing about, and managing the plugin and its version. If we don't specify the version, Maven 3.x prints a scary warning suggesting that in the future it will no longer enable our careless disregard for managing the nuts and bolts of its innards, so expect our build to break horribly.

As soon as you set any configuration option on a built-in component of Maven, you are forced to take responsibility for that component that you wouldn't have otherwise, and clutter your configuration with stuff that would otherwise be assumed by Maven.


Now suppose you want to run integration tests on your code, that run heavier weight tests in a separate phase after unit testing. The testing model we're following is to have lightweight unit tests that run fast for quick feedback on basic errors, then heavier tests which probably need a container like Jetty, and perhaps a database, to test a bit more deeply. It's often referred to as integration testing, meaning you're testing the components of a single application integrated together.

(However, the naming leads to confusion since integration testing can also refer to testing integration with external applications, web services, etc.)

So Maven supports running a separate set of tests in an integration test phase that runs after the unit tests, using the failsafe plugin. How do we make use of this? Firstly, we write JUnit test case classes, and name them ITMyClass, and the "IT" beginning tells Maven to run them in this phase.

So does Maven just find and run these classes if they exist? Of course not, you have to add some configuration to your pom.xml so it knows. Fair enough, Maven shouldn't waste our precious build time searching all of our test classes for ones named with "IT" if we aren't using integration tests.

So, it should be a pretty simple configuration setting to tell Maven we're using the integration test phase. Right?

Right?

<project>
  [...]
  <build>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-failsafe-plugin</artifactId>
        <version>2.7.2</version>
        <executions>
          <execution>
            <id>integration-test</id>
            <goals>
              <goal>integration-test</goal>
            </goals>
          </execution>
          <execution>
            <id>verify</id>
            <goals>
              <goal>verify</goal>
            </goals>
          </execution>
        </executions>
      </plugin>
    </plugins>
  </build>
  [...]
</project>

(From the failsafe plugin docs.)

So, no.

Holy scaffolding! All we've really told Maven with all that configuration is to include the "failsafe plugin", and wire it into the build lifecycle to run at the integration-test phase. But again, we've locked the plugin details, including the version number, into our project's pom file.

In this case, we've also had to explicitly (and verbosely) specify the lifecycle phase. So this is a plugin whose whole purpose in life is to be used in the integration phase of Maven's build lifecycle, abut we have to take it by the hand and tell it exactly how and where to wire itself into the lifecycle, even though we're following its default use case.

That isn't configuration over convention! That isn't even Mexico!

Oh, wait, this doesn't run our integration tests with a servlet container. Let's plug in Jetty. It's actually pretty easy, just copy, paste and tweak the example in this documenation from Codehaus (the second XML snippet on that page).

There is even a bit more meat in the configuration, which is fair enough, since there are a few things you might like to configure when running a servlet container. But you've still got loads more configuration, much of which is calling out details of Maven's execution lifecyle that are, when it comes down to it, the default for 90% of the people using this.

So you can see how your Maven project's pom.xml file quickly becomes packed with unneeded crap, which increases the barrier to understanding and modifying the build. Once you develop a pretty good understanding of Maven's internal structures and models you become adept at working out which parts of the pom.xml are scaffolding, and which you actually care about.

But most developers on a team won't invest the time to master Maven, and shouldn't need to. They have business value to deliver. This is the wall I'm hitting supporting a high performance development team, because there is a low tolerance for spending time on things other than building the application features customers need.

So is Ant any better than this? As I said in my previous post, Ant builds typically become over-complicated. But Maven projects not only suffer from complexity, they also suffer from Maven's inflexibility. Normally you'd consider trading off some flexibility for simplicity, as long as the end result was higher productivity.

So my next Maven post will focus on a specific area where Maven's inflexibility is hurting my team's productivity. I don't currently have more than these three posts in mind for this series, but then my team is still only in iteration 0.

Part 1 and Part 3 are here.

Feedback on these posts: I'm more than happy to be told I'm wrong, particularly if there are easier, simpler, and better ways to implement the examples I've described.  The best way to give feedback to comment below and mention me (@kief) in a Tweet, with a link to your response.


Source: http://kief.com/maven-configuration-complexity.html
Published at DZone with permission of Kief Morris, author and DZone MVB.

(Note: Opinions expressed in this article and its replies are the opinions of their respective authors and not those of DZone, Inc.)

Tags:

Comments

Andrew Spencer replied on Mon, 2012/01/09 - 6:45am

I agree in substance with almost everything you've written in these three posts.  In particular the verbosity and inflexibility.  About inflexibility, I would add that it's mostly in the fixed nature of the build lifecycle; the plugin architecture gives quite a lot of flexibility, but only up to the point where you need to do something that doesn't fit well with the lifecycle model.  And on Maven's good points, let's not forget that there was no standard mechanism for managing library dependencies before Maven was invented.

I do want to take issue with one detail of your criticisms.  You dislike that, as soon as you need non-default configuration for a plugin, you find yourself obliged to manage its version.  In truth, though, the problem is the other way round.   As long as you don't need any non-default configuration for a plugin (and practically everything in Maven is a plugin), then you're leaving Maven to manage your plugin versions.

And Maven does this using a rule that you probably don't want.  As I recall, it uses the version in your local repository if there is one, and if there isn't, it downloads the latest.  This means you can get breaking changes when you clean your local repository, or build on a new machine, without having changed anything in your code or POM.

Now, that's fine, as long as you don't need your builds to be repeatable...

 

 

 

Charlie Mordant replied on Mon, 2012/01/09 - 9:52am

Hi Kief,

 I completely agree with Andrew's comment.

But there are some missing points in this build tool that are very constraignant (integration tests is one of them).

 

Let me tell you a story where the use of maven is very complicated due to its lack of option.

Imagine you have a multi moduleMaven project:

Root

|-lib/myshared.jar

|-submodule1

|   |-.... and so on

|-submodule2

 

And some submodules must access to the root lib folder jar (at any depth).

 

How do you know where is the parent absolute path in maven via a submodule?

* You have to create a file on the root project.

* You have to use gmaven plugin recursively check the presence of that file in the folder tree

* You have to write a property (always with groovy script) in this file to point to your path

* You have to use the  properties maven plugin to access to the precedent property

Now, if you want to reference that path in your application:

* Create  an other file in src/main/resources containing this text:

my.prop=${parentrootabsolutepath}

* Use  the resource filtering to enable filter at build time (and test time :p)

* Reference the property in your application:

try {
				InputStream in = this.getClass().getResourceAsStream("/server.properties");
			    properties.load(in);
			} catch (IOException e) {
				System.out.println(e.getMessage());
			}
myprop= 
properties .getProperty("my.prop");

 

 

Isn't it a little tricky?

Waddle Efgjd replied on Mon, 2012/01/09 - 11:05am

Don't specifying version number of a plugin is evil as you'll fetch the LATEST (maven keyword) version, so if someone upload a new one, your version will change and maybe break your code.

No one does such a thing, not in a system (at least in production), not in software, never.

About lifecycle, yes it's a bit verbose, but it's more an XML problem than maven one. How would you express the attachment to a lifecycle phase ?

Maven's lifecyle is THE key to replace ANT's makefile logic. Everything is standard, and it's giving you hooks to attach extra behaviour at any time (phase) of the build.

" That isn't configuration over convention!" : you meant the contrary I think...

By the way, Maven doesn't provide convention over configuration (which IMHO is evil too). It provides you STANDARDS. You must be explicit in your pom.xml, it's just that everyone is doing the same so the learning curve is just low.

Curtis Yanko replied on Mon, 2012/01/09 - 11:43am

I do find Maven bashing amusing. I will however confess to admitting that Maven is at the very least, flawed. Still, for me, working in large enterprises it has been much more of a blessing than a bane.

 Site reports alone are worth the price of admission! The ability to produce a Bill of Materials is all by itself a huge leap forward. No more checking JARs into the SCC tool and no more include *.jar. If I had a dollar for every project I've *maven-ized* only to learn that no on on the team actualy knew what the dependencies were...   ...well...   ...ok, I'd have about $100 or so. :-)  

Lets not forget that Source + Tools = Product so being *locked in* to a specific version of a plugin is a good thing (see BOM reference above). I used to let these float on the LATEST version until builds started breaking with no code changes because a new plugin was released. No faster way to lose credibility as a build service than to have it be un-reliable, let alone an un-repeatable one.

I also love that my developers don't have to re-learn the build process when they change teams. It also means we, as a centralized service, can partner and collaborate with Dev teams. They're not on their own when it comes to the build scripts. And before you try and tell me Ant dosn't have to be re-learned I'd argue that I've seen a lot of Ant implimentations and no two of them were quite the same, some were quite good but most were really awful, brittle and codified really bad practices.

Dependenchy management will be Maven's legacy tough admittedly tools like Buildr and Gradle will likely take that legacy forward.

Is it really too much scaffolding? 

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.