How to manage dependencies in a Gradle multi-project build

gradleI’ve been a fan of the Gradle build tool from quite early on. Its potential was clear even before the 1.0 version, when changes were regularly breaking. Today, upgrading rarely cause surprises. The tool has become mature and performs well.

Gradle includes a powerful dependency management system that can work with Maven and Ivy repositories as well as local file system dependencies.

During my work with Gradle I’ve come to rely on a pattern for managing dependencies in a multi-project build that I want to share. This pattern consists of two key practices:

  1. Centralize dependency declarations in build.gradle
  2. Centralize dependency version declarations in gradle.properties

Both practices are examples of applying software development best practices like DRY to the code that makes up the Gradle build. Let’s look at them in some more detail.

Centralize dependency declarations

In the root project’s build.gradle file, declare a new configuration for each dependency used in the entire project. In each sub-project that uses the dependency, declare that the compile (or testCompile, etc) configuration extends the configuration for the dependency:

subprojects {
  configurations {
    commonsIo
  }

  dependencies {
    commonsIo 'commons-io:commons-io:2.5'
  }
}
configurations {
  compile.extendsFrom commonsIo
}

By putting all dependency declarations in a single place, we know where to look and we prevent multiple sub-projects from declaring the same dependency with different versions.

Furthermore, the sub-projects are now more declarative, specifying only what logical components they depend on, rather than all the details of how a component is built up from individual jar files. When there is a one-to-one correspondence, as in the commons IO example, that’s not such a big deal, but the difference is pronounced when working with components that are made up of multiple jars, like the Spring framework or Jetty.

Centralize dependency version declarations

The next step is to replace all the version numbers from the root project’s build.gradle file by properties defined in the root project’s gradle.properties:

dependencies {
  commonsIo "commons-io:commons-io:$commonsIoVersion"
}
commonsIoVersion=2.5

This practice allows you to reuse the version numbers for related dependencies. For instance, if you’re using the Spring framework, you may want to declare dependencies on spring-mvc and spring-jdbc with the same version number.

There is an additional advantage of this approach. Upgrading a dependency means updating gradle.properties, while adding a new dependency means updating build.gradle. This makes it easy to gauge from a commit feed what types of changes could have been made and thus to determine whether a closer inspection is warranted.

You can take this a step further and put the configurations and dependencies blocks in a separate file, e.g. dependencies.gradle.

And beyond…

Having all the dependencies declared in a single location is a stepping stone to more advanced supply chain management practices.

The centrally declared configurations give a good overview of all the components that you use in your product, the so-called Bill of Materials (BOM). You can use the above technique, or use the Gradle BOM plugin.

The BOM makes it easier to use a tool like OWASP DependencyCheck to check for publicly disclosed vulnerabilities in the dependencies that you use. At EMC, about 80% of vulnerabilities reported against our products are caused by issues in 3rd party components, so it makes sense to keep a security eye on dependencies.

A solid BOM also makes it easier to review licenses and their compliance requirements. If you can’t afford a tool like BlackDuck Protex, you can write something less advanced yourself with modest effort.

FizzBuzz Kata With Java Streams

black-beltAfter only a couple of weeks of Judo practice, my son got bored. He complained that he wasn’t learning anything, because he kept doing the same thing over and over.

It’s not just young children that confuse learning and doing new things. For instance, how many software developers go through the trouble of deliberate practice by performing katas or attending dojos?

It may seem silly to repeat exercises that you’ve already done many times, but it’s not. It’s the only way to become a black belt in your field. And remember that mastery is one of the three intrinsic motivators (the others being autonomy and purpose).

Practicing means slowing down and moving focus from outcome to process. It’s best to use simple exercises that you can complete in a limited amount of time, so you can do the same exercise multiple times.

I’ve found that I virtually always learn something new when I practice. That’s not because I’ve forgotten how to solve the problem since last time, but because I’ve learned new things since then and thus see the world through new eyes.

For example, since Java 8 came out I’ve been trying to use the new stream classes to help move to a more functional style of programming. This has changed the way I look at old problems, like FizzBuzz.

Let’s see this in action. Of course, I start by adding a test:

+ package remonsinnema.blog.fizzbuzz;
+
+ import static org.junit.Assert.assertEquals;
+
+ import org.junit.Test;
+
+
+ public class WhenFizzingAndBuzzing {
+
+   private final FizzBuzz fizzbuzz = new FizzBuzz();
+
+   @Test
+   public void shouldReplaceWithFizzAndBuzz() {
+     assertEquals(“1”, “1”, fizzbuzz.get(1));
+   }
+
+ }

This test uses the When…Should form of unit testing that helps focus on behavior rather than implementation details. I let Eclipse generate the code required to make this compile:

+ package remonsinnema.blog.fizzbuzz;
+
+
+ public class FizzBuzz {
+
+   public String get(int i) {
+     return null;
+   }
+
+ }

The simplest code that makes the test pass is to fake it:

  package remonsinnema.blog.fizzbuzz;
  public class FizzBuzz {
    public String get(int i) {
–     return null;
+     return “1”;
    }
  }

Now that the test passes, it’s time for refactoring. I remove duplication from the test:

  public class WhenFizzingAndBuzzing {
    @Test
    public void shouldReplaceWithFizzAndBuzz() {
–     assertEquals(“1”, “1”, fizzbuzz.get(1));
+     assertFizzBuzz(“1”, 1);
+   }
+
+   private void assertFizzBuzz(String expected, int n) {
+     assertEquals(Integer.toString(n), expected, fizzbuzz.get(n));
    }
  }

Next I add a test to force the real implementation:

  public class WhenFizzingAndBuzzing {
    @Test
    public void shouldReplaceWithFizzAndBuzz() {
      assertFizzBuzz(“1”, 1);
+     assertFizzBuzz(“2”, 2);
    }
    private void assertFizzBuzz(String expected, int n) {
  package remonsinnema.blog.fizzbuzz;
  public class FizzBuzz {
–   public String get(int i) {
–     return “1”;
+   public String get(int n) {
+     return Integer.toString(n);
    }
  }

OK, now let’s get real with a test for Fizz:

  public class WhenFizzingAndBuzzing {
    public void shouldReplaceWithFizzAndBuzz() {
      assertFizzBuzz(“1”, 1);
      assertFizzBuzz(“2”, 2);
+     assertFizzBuzz(“Fizz”, 3);
    }
    private void assertFizzBuzz(String expected, int n) {
  package remonsinnema.blog.fizzbuzz;
  public class FizzBuzz {
    public String get(int n) {
+     if (n == 3) {
+       return “Fizz”;
+     }
      return Integer.toString(n);
    }

Similar for Buzz:

  public class WhenFizzingAndBuzzing {
      assertFizzBuzz(“Fizz”, 3);
+     assertFizzBuzz(“4”, 4);
+     assertFizzBuzz(“Buzz”, 5);
    }
    private void assertFizzBuzz(String expected, int n) {
  public class FizzBuzz {
      if (n == 3) {
        return “Fizz”;
      }
+     if (n == 5) {
+       return “Buzz”;
+     }
      return Integer.toString(n);
    }

Here I just copied and pasted the if statement to get it working quickly. We shouldn’t stop there, of course, but get rid of the dirty stuff. In this case, that’s duplication.

First, let’s update the code to make the duplication more apparent:

  package remonsinnema.blog.fizzbuzz;
  public class FizzBuzz {
    public String get(int n) {
–     if (n == 3) {
–       return “Fizz”;
+     MultipleReplacer replacer = new MultipleReplacer(3, “Fizz”);
+     if (n == replacer.getValue()) {
+       return replacer.getText();
      }
–     if (n == 5) {
–       return “Buzz”;
+     replacer = new MultipleReplacer(5, “Buzz”);
+     if (n == replacer.getValue()) {
+       return replacer.getText();
      }
      return Integer.toString(n);
    }
+ package remonsinnema.blog.fizzbuzz;
+
+
+ public class MultipleReplacer {
+
+   private final int value;
+   private final String text;
+
+   public MultipleReplacer(int value, String text) {
+     this.value = value;
+     this.text = text;
+   }
+
+   public int getValue() {
+     return value;
+   }
+
+   public String getText() {
+     return text;
+   }
+
+ }

I just created a new value object to hold the two values that I had to change after the copy/paste.

Now that the duplication is clearer, it’s easy to remove:

  package remonsinnema.blog.fizzbuzz;
+ import java.util.Arrays;
+ import java.util.Collection;
+
  public class FizzBuzz {
+   private final Collection<MultipleReplacer> replacers = Arrays.asList(
+       new MultipleReplacer(3, “Fizz”), new MultipleReplacer(5, “Buzz”));
+
    public String get(int n) {
–     MultipleReplacer replacer = new MultipleReplacer(3, “Fizz”);
–     if (n == replacer.getValue()) {
–       return replacer.getText();
–     }
–     replacer = new MultipleReplacer(5, “Buzz”);
–     if (n == replacer.getValue()) {
–       return replacer.getText();
+     for (MultipleReplacer replacer : replacers) {
+       if (n == replacer.getValue()) {
+         return replacer.getText();
+       }
      }
      return Integer.toString(n);
    }

I’m not done cleaning up, however. The current code suffers from feature envy, which I resolve by moving behavior into the value object:

  package remonsinnema.blog.fizzbuzz;
  import java.util.Arrays;
  import java.util.Collection;
+ import java.util.Optional;
  public class FizzBuzz {
    public String get(int n) {
      for (MultipleReplacer replacer : replacers) {
–       if (n == replacer.getValue()) {
–         return replacer.getText();
+       Optional<String> result = replacer.textFor(n);
+       if (result.isPresent()) {
+         return result.get();
        }
      }
      return Integer.toString(n);
  package remonsinnema.blog.fizzbuzz;
+ import java.util.Optional;
+
  public class MultipleReplacer {
      this.text = text;
    }
–   public int getValue() {
–     return value;
–   }
–   public String getText() {
–     return text;
+   public Optional<String> textFor(int n) {
+     if (n == value) {
+       return Optional.of(text);
+     }
+     return Optional.empty();
    }
  }

Now that I’m done refactoring, I can continue with multiples:

  public class WhenFizzingAndBuzzing {
      assertFizzBuzz(“Fizz”, 3);
      assertFizzBuzz(“4”, 4);
      assertFizzBuzz(“Buzz”, 5);
+     assertFizzBuzz(“Fizz”, 6);
    }
    private void assertFizzBuzz(String expected, int n) {
  public class MultipleReplacer {
    }
    public Optional<String> textFor(int n) {
–     if (n == value) {
+     if (n % value == 0) {
        return Optional.of(text);
      }
      return Optional.empty();

The final test is for simultaneous “Fizz” and “Buzz”:

  public class WhenFizzingAndBuzzing {
      assertFizzBuzz(“4”, 4);
      assertFizzBuzz(“Buzz”, 5);
      assertFizzBuzz(“Fizz”, 6);
+     assertFizzBuzz(“7”, 7);
+     assertFizzBuzz(“8”, 8);
+     assertFizzBuzz(“Fizz”, 9);
+     assertFizzBuzz(“Buzz”, 10);
+     assertFizzBuzz(“11”, 11);
+     assertFizzBuzz(“Fizz”, 12);
+     assertFizzBuzz(“13”, 13);
+     assertFizzBuzz(“14”, 14);
+     assertFizzBuzz(“FizzBuzz”, 15);
    }
    private void assertFizzBuzz(String expected, int n) {
  public class FizzBuzz {
        new MultipleReplacer(3, “Fizz”), new MultipleReplacer(5, “Buzz”));
    public String get(int n) {
+     StringBuilder result = new StringBuilder();
      for (MultipleReplacer replacer : replacers) {
–       Optional<String> result = replacer.textFor(n);
–       if (result.isPresent()) {
–         return result.get();
+       Optional<String> replacement = replacer.textFor(n);
+       if (replacement.isPresent()) {
+         result.append(replacement.get());
        }
      }
+     if (result.length() > 0) {
+       return result.toString();
+     }
      return Integer.toString(n);
    }

This code is rather complex, but this is where streams come to the rescue:

  public class FizzBuzz {
        new MultipleReplacer(3, “Fizz”), new MultipleReplacer(5, “Buzz”));
    public String get(int n) {
–     StringBuilder result = new StringBuilder();
–     for (MultipleReplacer replacer : replacers) {
–       Optional<String> replacement = replacer.textFor(n);
–       if (replacement.isPresent()) {
–         result.append(replacement.get());
–       }
–     }
–     if (result.length() > 0) {
–       return result.toString();
–     }
–     return Integer.toString(n);
+     return replacers.stream()
+         .map(replacer -> replacer.textFor(n))
+         .filter(Optional::isPresent)
+         .map(optional -> optional.get())
+         .reduce((a, b) -> a + b)
+         .orElse(Integer.toString(n));
    }
  }

Note how the for and if statements disappear. Rather than spelling out how something needs to be done, we say what we want to achieve.

We can apply the same trick to get rid of the remainingif statement in our ode base:

  public class MultipleReplacer {
    }
    public Optional<String> textFor(int n) {
–     if (n % value == 0) {
–       return Optional.of(text);
–     }
–     return Optional.empty();
+     return Optional.of(text)
+         .filter(ignored -> n % value == 0);
    }
  }

The code is on GitHub.