Test-Driven Decoupling

Agile India 2013

Owen Rogers / @exortech



Owen Rogers / @exortech

Product Lead @ Pulse Energy

Practicing Agile/XP since 2000

Formerly an agile coach with ThoughtWorks

Conference organizer for original Agile India conference

Anyone know what this is?

This one's for the sci-fi geeks

Dealing with monolithic systems





characterized by massiveness, total uniformity, rigidity, invulnerability, etc.

Where does the Monolith come from?

Evolving systems

Canalizing Design


Make HAL do something else

function askHAL(action) {
	if (isReadMe(action) {
		return "Affirmative, Dave. I read you.";
	} else if (isReadMessage(action)) {
		return "It is dangerous to remain here. You must leave within two days.";
	return "I'm sorry, Dave. I'm afraid I can't do that.";

Path of least resistance


  1. Grow by accumulation or coalescence
  2. Form (a composite whole or a collection of things) by gradual accumulation

Big Ball Of Mud

Problems of Monolithic Systems

  • Code encumbrance
  • Regression cost
  • Scaling
  • Resilience

What's the alternative?

Service-oriented architecture

SOA Nirvana - WHEE!

Meanwhile, back in the real world...

Barriers to SOA

Design barriers

  • Domain model is evolving
  • Code base is rapidly evolving
  • Service context boundaries
  • Conway's law
  • Overhead of splitting and merging services

Technology barriers

  • Message protocol (JSON? BJSON? BSON? Protobuf? Thrift? SOAP?)
  • Message design
  • Serialization/deserialization overhead
  • Message versioning
  • Language/library support

Deployment barriers

  • Dependency management
  • Service versioning
  • Interdependent builds
  • Multiple packages to deploy
  • Monitoring and support

So, how do we get from

Or Even


We want to split the monolith

But how?

Easy right?

  1. Pull related functionality into a separate module
  2. Package module as a service
  3. Deploy
  4. Profit!

Not so fast, Dave

Alien goo

First, a brief digression...

Who hasn't read this book?

Package design

Package = directory, namespace, module, etc.

Maximize cohesion within a package

Minimize coupling

Look familiar?

What's wrong with this model?

Let's look inside

Ewww - maximum alien goo

Structure packages by domain context

Makes extracting into services much easier!

So back to


Modularization Process

  1. Pull related functionality into a common package (maximize cohesion)
  2. Restructure code to sever unnecessary external dependencies (minimize coupling)
  3. Extract package into an independent component/module
  4. Servicify module
  5. Profit!

Great! But this is hard...

Test-driven decoupling

(to the rescue!)

Test-driven decoupling

Use tests to enforce package structure and support decoupling effort


Free, open source tools that you are probably already using





MaDGe - Module Dependency Graph


What about external dependencies?

I'm sorry, Dave. This mission is too important for me to allow you to jeopardize it.

Focus on the code you own



Example 1: Starting point

core → util

  public void corePackageShouldOnlyDependOn_util() throws Exception {
    Set<String> valid = ImmutableSortedSet.of("com.pulseenergy.util");
    assertThat(findInternalDependencies("com.pulseenergy.core"), is(valid));

system → { util, core }

  public void systemPackageShouldOnlyDependOn_core_util() throws Exception {
    Set<String> valid = ImmutableSortedSet.of(
      "com.pulseenergy.core", "com.pulseenergy.util");
    assertThat(findInternalDependencies("com.pulseenergy.system"), is(valid));

Example 2: Invalid dependency

security → { core, spring, system, util }

  public void securityPackageShouldOnlyDependOn_core_springSecurity_system_util() {
    Set<String> valid = ImmutableSortedSet.of(
      "com.pulseenergy.core", "com.pulseenergy.spring.security",
      "com.pulseenergy.system", "com.pulseenergy.util");
    Set<String> invalid = ImmutableSortedSet.of("com.pulseenergy.web.filter"); // invalid dep
      is(ImmutableSortedSet.of(valid, invalid)));

Test Writing Process

  1. Write a failing test to capture the existing dependencies (Red bar)
  2. Assert all existing dependencies (Green bar)
  3. Split dependencies into valid and invalid sets
  4. Remove one invalid dependencies from the set (Red bar)
  5. Break that dependencies in the code (Green bar)
  6. Lather (commit) and repeat

1. Capture the existing dependencies: foo → ???

  public void fooShouldDependOnX() {

2. assert existing dependencies (green bar)

  public void fooShouldDependOnX() {
    	is(ImmutableSortedSet.of("com.pulseenergy.bar", "com.pulseenergy.baz")));

3. split into valid/invalid sets (green bar)

  public void fooShouldDependOnBar() {
    Set<String> valid = ImmutableSortedSet.of("com.pulseenergy.bar");
    Set<String> invalid = ImmutableSortedSet.of("com.pulseenergy.baz");
    	is(Iterables.concat(valid, invalid)));

4. remove invalid dependency from test (red bar)

  public void fooShouldDependOnBar() {
    Set<String> valid = ImmutableSortedSet.of("com.pulseenergy.bar");
    assertThat(findInternalDependencies("com.pulseenergy.foo"), is(valid));

5. remove invalid dependency from code (green bar)

Breaking dependencies can take a long time

Commit tests with invalid dependencies listed

Run these tests in your build

You broke the build, Dave █

When package is suitably decoupled, service-ify!


owen@exortech.com | @exortech

For serious geeks only

Under the hood

private Collection<JavaPackage> analyzePackage(String targetPackage) throws IOException {
	final JDepend jDepend = new JDepend();
		 + "/" + convertPackageToPath(targetPackage));
	final Collection<JavaPackage> allPackages = jDepend.analyze();
	final List<JavaPackage> filteredPackages = Lists.newArrayList();
	for (JavaPackage aPackage : allPackages) {
		if (aPackage.getName().startsWith(targetPackage)) {
	return filteredPackages;

Under the hood

private Set<String> findInvalidDependencies(JavaPackage javaPackage) {
	final Set<String> packageViolations = Sets.newTreeSet();
	Collection<JavaPackage> efferents = javaPackage.getEfferents();
	for (JavaPackage efferentPackage : efferents) {
		if (isInternalDependency(efferentPackage)) {
	return packageViolations;