Automation for the people: Asserting architectural soundness

Take charge of your architecture by using a proactive build process

Is your software architecture what you think it is? The designs we communicate to each other aren't always what we expect when it comes to source code. Paul Duvall returns from his hiatus in this installment of Automation for the people to demonstrate how you can discover architectural deviations by writing tests using JUnit, JDepend, and Ant to discover problems proactively instead of long after the fact.

Share:

Paul Duvall (paul.duvall@stelligent.com), CTO, Stelligent Incorporated

Paul DuvallPaul Duvall is the CTO of Stelligent Incorporated, a consulting firm and thought leader in helping development teams optimize Agile software production. He is a contributing author to the UML 2 Toolkit, co-author of No Fluff Just Stuff Anthology (Pragmatic Programmers, 2007) and co-author of the Addison-Wesley Signature Series book, Continuous Integration: Improving Software Quality and Reducing Risk (Addison-Wesley, June 2007).



10 July 2007

Also available in Chinese

Of the many software development projects I've worked on and observed, one thing is almost a constant: The architecture you think you have is often different than what you actually have.

Analyzing code metric reports, like those created by tools such as JDepend (see Resources), can be effective in determining if your code adheres to an established architecture. Some teams reverse-engineer their code into UML diagrams for the same effect, and others even use an IDE that generates these same artifacts while coding -- real-time reverse-engineering. Yet, all of these approaches are still reactive. You must manually review and analyze a report or diagram to determine an architectural violation, which in some cases could be long after the fact.

Imagine you received a notification every time some portion of code violated an intended architecture -- such as through an Ant build script failure -- as demonstrated in Listing 1:

Listing 1. Build failure based on architecture violation
...
BUILD FAILED
...
build.xml:35 Test ArchitecturalRulesTest failed

Total time: 20 seconds

About this series

As developers, we work to automate processes for end-users; yet, many of us overlook opportunities to automate our own development processes. To that end, Automation for the people is a series of articles dedicated to exploring the practical uses of automating software development processes and teaching you when and how to apply automation successfully.

This article covers techniques that enable you to proactively analyze a software architecture through the beauty of build automation. What's more, examples demonstrate how to fail a build process based on rules that you can define using JDepend's API with JUnit.

The best part, of course, is that through build automation, you and your team can discover when source code violates an established architecture early in the development life cycle. That's what I call taking-charge of an architecture!

Reactively setting the stage

Figure 1 illustrates a common architectural pattern for building Web applications. The presentation layer (meaning a group of related packages) has dependencies on the controller layer, the controller layer depends on the domain and business layers, and lastly, the business layer depends on the data and domain layers.

Figure 1. An architectural layering diagram for a typical web application
architectural layering diagram

So far, so good, right? Yet, as I'm sure you've seen before, these common best practice idioms can get lost in the shuffle of day-to-day software development. In fact, this can happen quite easily (as well as quickly).

For example, Figure 2 illustrates a subtle violation to this example architecture; in this case, the data layer is now making at least one call to the business layer:

Figure 2. The Data layer is now calling an object in the Business layer, creating an architectural violation
architectural violation

This slight change in the architecture has the unintended effect of making modifications more difficult. In fact, now modifications to one area of code may require changing many other areas. For example, if you eliminate or change some methods in the business layer classes, you may need to remove references from the data layer too. The challenges with change only get worse as more violations occur.

Using traditional monitoring techniques, like viewing a JDepend or Macker report (see Resources), how quickly would you spot a deviation to the intended design? If you are like me and want to produce working software quickly, then the faster you can spot issues that will affect delivery speed, the better, don't you think?


Voodoo with JDepend is easy

One of the easiest tools that facilitates assessing architecture violations is JDepend. This open source tool has been around for years and integrates well with Ant and Maven; moreover, it supports a rich Java™ API for more fine-grained interaction. But, as I've already mentioned, its reports are passive by nature. Depending on how often you actually run (and view) them, architecture violations may go unnoticed until it becomes quite difficult to rectify them.

Afferent coupling vs. efferent coupling

In JDepend, afferent coupling is represented as the number of packages that rely on an analyzed package. For instance, if you're using a logging framework or a Web framework like Struts, you'd expect that these packages would have a high afferent coupling because there'd be many packages throughout the code base that rely on these frameworks. Efferent coupling, the opposite of afferent coupling, is represented as the number of packages an analyzed package depends upon; in other words, how many dependent packages it has.

Asserting architectures

Proactively determining if an architecture is inappropriately changing is really a matter of studying a particular package's coupling. In fact, by monitoring both the afferent and efferent coupling of key packages within a software architecture and watching for deviations to expected values, you can easily signal a modification gone awry.

For example, in the modification demonstrated in Figure 2, the new efferent coupling of the data layer is now greater than 0 because this layer is now communicating directly to the business layer. This coupling, of course, is through a simple import (and its use) in some class residing in the data layer. Luckily, it's easy to spot something like this with JUnit, JDepend's nifty API, and a bit of build magic.

It turns out that within the context of a build (like Ant or Maven), you can run a JUnit test that utilizes JDepend's API to proactively spot changes to coupling values; moreover, if these changes aren't valid, you can then fail the build. This is as proactive as it gets, don't you think?

The first step is to create a JUnit test and appropriately configure JDepend, as shown in Listing 2:

Listing 2. Setting up JDepend in JUnit
import junit.framework.TestCase;
import jdepend.framework.JavaPackage;
import jdepend.framework.JDepend;

public class ArchitecturalRulesTest extends TestCase {
  private static final String DIRECTORY_TO_ANALYZE = "C:/dev/project-sandbox/brewery/classes";
  private JDepend jdepend;
  private String dataLayer = "com.beer.business.data";
  private String businessLayer = "com.beer.business.service";
  private Collection dataLayerViolations = new ArrayList<String>();

  public ArchitecturalRulesTest(String name) {
    super(name);
  }

  protected void setUp() throws IOException {
    jdepend = new JDepend();
    jdepend.addDirectory(DIRECTORY_TO_ANALYZE);
    // Calling the businessLayer from the dataLayer is a violation
    dataLayerViolations.add(businessLayer);
  }

There is a lot going on so far in Listing 2, so let me summarize a few points:

  • Two JDepend classes are required: jdepend.framework.JavaPackage and jdepend.framework.JDepend.
  • The location of the source classes to analyze is defined in the DIRECTORY_TO_ANALYZE constant. JDepend scans this directory by calling JDepend.addDirectory, which is done via a fixture (a la the setUp() method).
  • The packages to analyze are defined in the "Layer" Strings.
  • The dataLayerViolationsCollection adds the businessLayer String (which represents a package) to indicate this is a violation to the intended architecture.

Using these four points, I've effectively set up JDepend to work its magic on a particular code base. Now I've got to do some precise logic to account for changes in coupling values.

The testDataLayer() test case in Listing 3 is the heart of my architecture assertion mojo. This method determines if there are any violations to the dataLayer -- if the isLayeringValid() method (defined next in Listing 4) returns false, the test case is considered a failure, as there must be a violation to the architecture.

Listing 3. Test case validation a la JDepend
public void testDataLayer() {
  if (!isLayeringValid(dataLayer, dataLayerViolations)) {
    fail("Dependency Constraint failed in Data Layer");
  }
}

Listing 4 is the method called by the test case in Listing 3:

Listing 4. Finding the Afferent coupling of each package via looping
private boolean isLayeringValid(String layer, Collection rules) {
  boolean rulesCorrect = true;
  Collection packages = jdepend.analyze();
  Iterator itor = packages.iterator();
  JavaPackage jPackage = null;
  String analyzedPackageName = null;
  while (itor.hasNext()) {
    jPackage = (JavaPackage) itor.next();
    analyzedPackageName = jPackage.getName();
    Iterator afferentItor = jPackage.getAfferents().iterator();
    String afferentPackageName = null;
    while (afferentItor.hasNext()) {
      JavaPackage afferentPackage = (JavaPackage) afferentItor.next();
      afferentPackageName = afferentPackage.getName();
    }
    rulesCorrect = isEfferentsValid(layer, rules, rulesCorrect, jPackage, analyzedPackageName);
  }
  return rulesCorrect;
}

The purpose of the isLayeringValid() method is to determine the afferent coupling of all packages found in the DIRECTORY_TO_ANALYZE directory from Listing 2. As you can see toward the bottom of the listing, this method then defers to the isEfferentsValid() method, which is shown in Listing 5.

Here the isEfferentsValid() method flags a package as an architectural violation if it finds it does not adhere to the prescribed package dependency (because there is an efferent coupling greater than zero from one package to the other) by using the dataLayerViolations collection from Listing 2. This is what indirectly causes the testDataLayer() test case (from Listing 3) to fail, by the way.

Listing 5. Determining package dependency violations
private boolean isEfferentsValid(String layer, Collection rules,
  boolean rulesCorrect, JavaPackage jPackage, String analyzedPackageName) {
  Collection efferents = jPackage.getEfferents();
  Iterator efferentItor = efferents.iterator();
  while (efferentItor.hasNext()) {
    JavaPackage efferentPackage = (JavaPackage) efferentItor.next();
	String efferentPackageName = efferentPackage.getName();
	for (Iterator it = rules.iterator(); it.hasNext();) {
	  String value = (String) it.next();
	  if (analyzedPackageName.equals(layer)
  	    && efferentPackageName.equals(value)) {
	    rulesCorrect = false;
	    System.out.println("TEST FAILURE: "
  	      + analyzedPackageName
	      + " should not depend upon (have an efferent coupling to) "
	      + efferentPackageName);
	    break;
	  }
    }
  }
  return rulesCorrect;
}

As you can see, Listings 2 through 5 work in concert to essentially scan a series of packages for changes to coupling; if there is a change, a failure condition is triggered and consequently, JUnit reports a failure. Quite impressive, if you ask me!

Don't forget to run that test automatically

Once you've written this sort of constraint-based test a la JUnit working in concert with JDepend, you can run it as a part of your build process using tools like Ant or Maven. For example, Listing 6 demonstrates running a series of these tests via Ant. The test.dependency.dir property maps to my root/src/test/java/dependency directory, which contains those magic architecture verifiers.

Listing 6. An Ant script that runs dependency constraint tests
<target name="run-tests" depends="compile-tests"> 
  <mkdir dir="${logs.junit.dir}" />
  <junit fork="yes" haltonfailure="true" dir="${basedir}" printsummary="yes">
    <classpath refid="test.class.path" />
    <classpath refid="project.class.path"/>
    <formatter type="plain" usefile="true" />
    <formatter type="xml" usefile="true" />
    <batchtest fork="yes" todir="${logs.junit.dir}">
      <fileset dir="${test.dependency.dir}">
        <patternset refid="test.sources.pattern"/>
      </fileset>
    </batchtest>
  </junit>    
</target>

The JDepend JAR must be present in Ant's classpath for the JUnit tests to execute successfully. The haltonfailure attribute is set to true so that the build stops on test failure too.


Threshold-driven architectures

I've already indicated how the passive approach to architectural adherence can be difficult to conduct without significant effort, plus I hope I've convinced you how easy it is for a violation to slip through the cracks. By executing architectural tests as part of your build process, you can make these types of checks automatic and repeatable. Isn't is nice how Figure 3 shows a build failure after running Ant? I don't even have to view a JDepend report if I don't want to.

Figure 3. Build failure upon architectural violation
build failure

The beauty of this sort of proactive monitoring is that you can fix an architectural layering problem immediately after it's discovered. The closer the fix is made to when the issue occurred reduces risks too -- not to mention costs. In essence, your team doesn't miss a beat and can still work diligently towards releasing working software quickly.


Automation for the architecture

How else can I use JDepend's magic?

There are several ways to add proactive checks via JDepend. In fact, JDepend recommends using its DependencyConstraint class. Although using DependencyConstraint is a much simpler implementation, I chose not to use it because it would only demonstrate one approach of using its API to enforce architectural rules and it was not reliably working based on my requirements. There are other tools that support package dependency adherence; see Resources for details.

Now you can use your build process to proactively discover design violations to your intended architecture. Plus, I've just shown you one of a few possible examples -- you can certainly get creative and analyze measures like package Instability to facilitate determining the overall robustness of an architecture.

The approach I've outlined is a simple way to reduce the need to continually reverse-engineer code and analyze diagrams to determine architectural adherence. If you are using a Continuous Integration system, you can use these tests as a safety net to ensure the code checked into the version control system passes these architectural rules -- every time a change is applied. If you make changes to the architecture, simply change the rules in your JUnit tests to ensure your team is following project standards. Now that's what I call asserting architectural soundness the proactive way.

Resources

Learn

Get products and technologies

  • Ant: Run your Java build with Ant.
  • JDepend: Download JDepend to analyze package dependencies.
  • Macker: Download Macker for build-time architectural rule checking.
  • Japan: Provides an Ant task and IntelliJ plug-in for analyzing package dependencies using an XML configuration.

Discuss

  • Improve Your Code Quality discussion forum: Regular developerWorks contributor Andrew Glover brings his considerable expertise as a consultant focused on improving code quality to this moderated discussion forum.
  • Accelerate development space: Regular developerWorks contributor Andrew Glover hosts a one stop portal for all things related to developer testing, Continuous Integration, code metrics, and refactoring.

Comments

developerWorks: Sign in

Required fields are indicated with an asterisk (*).


Need an IBM ID?
Forgot your IBM ID?


Forgot your password?
Change your password

By clicking Submit, you agree to the developerWorks terms of use.

 


The first time you sign into developerWorks, a profile is created for you. Information in your profile (your name, country/region, and company name) is displayed to the public and will accompany any content you post, unless you opt to hide your company name. You may update your IBM account at any time.

All information submitted is secure.

Choose your display name



The first time you sign in to developerWorks, a profile is created for you, so you need to choose a display name. Your display name accompanies the content you post on developerWorks.

Please choose a display name between 3-31 characters. Your display name must be unique in the developerWorks community and should not be your email address for privacy reasons.

Required fields are indicated with an asterisk (*).

(Must be between 3 – 31 characters.)

By clicking Submit, you agree to the developerWorks terms of use.

 


All information submitted is secure.

Dig deeper into Java technology on developerWorks


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=1
Zone=Java technology
ArticleID=239611
ArticleTitle=Automation for the people: Asserting architectural soundness
publish-date=07102007