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 |
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!
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
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
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?
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.
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.JavaPackageandjdepend.framework.JDepend.
- The location of the source classes to analyze is defined in the
DIRECTORY_TO_ANALYZEconstant. JDepend scans this directory by callingJDepend.addDirectory, which is done via a fixture (a la thesetUp()method).
- The packages to analyze are defined in the "Layer"
Strings.
- The
dataLayerViolationsCollectionadds thebusinessLayerString(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
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
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.
Learn
- "Managing Your Dependencies with JDepend" (Glen Wilcox, OnJava, January 2004): In this article, Glen Wilcox introduces JDepend, a freely available tool that can provide insight into several qualities of your software architecture.
-
Separation of Concerns: Building an architecture that adheres to this principle is considered a best practice.
- "
In pursuit of code
quality: Code quality for software architects" (Andrew Glover, developerWorks, April 2006): Use coupling metrics to support your system architecture.
- "Forcing build failures with simple thresholds" (TestEarly): Automated build systems (like Ant) are excellent for introducing proactive quality gates.
- "JDepend" (Mike Clark, Clarkware Consulting): See the section on Dependency Constraint Tests with JUnit.
- "Architectural complexity" (Grady Booch, IBM Rational): "Can (you) meaningfully compare the complexity of the architecture of one software-intensive system to another?"
-
Automation for the people
(Paul Duvall, developerWorks): Read the complete series.
-
developerWorks: Hundreds of articles about every aspect of Java programming.
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.

Paul 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).





