Monitoring and Enforcing Design Dependencies with Verifydesign

lthough software and architectural reuse has been a constant drumbeat in the technical press for many years, it’s still common to see code and designs that violate many principles of reuse. For example, how many software systems have you seen that are similar to the diagram in Figure 1? Or when trying to reuse code, you find an exisiting component with functionality you need (Component E), but you also find it impossible to reuse the component because it depends on Component C?which your new system doesn’t need. You might add that component only to find that, in turn, Component C depends upon Component A.

Such interdependencies are still typical of many software projects today. Of course, such code isn’t generally produced on purpose. A team typically lays out the architecture, then starts coding up the system. As they code, bad dependencies unintentionally slip in?sometimes that just happens?and the team often doesn’t even discover such unwanted dependencies until later in the development cycle.

 
Figure 1. A Typical Software System: The figure illustrates the tangled relationships between components in many software systems.
 
Figure 2. An Ideal Software System: Note the linear relationship path between components in this application.

Now a new open-source tool called “Verifydesign” is available to monitor code to help prevent unwanted dependencies. Figure 2 shows a more appetizing set of relationships between software components. In Figure 2, the yellow bars represent the APIs. Components communicate only through these APIs. I intentionally drew lines in Figure 1 from the middle of a component to the middle of another component because that’s the reality of most software systems today. Figure 1 emphasizes that many software systems don’t bother communicating through the API, but instead go directly to the middle of a component as shown. In contrast, Figure 2 shows a design without the circular dependencies of Figure 1.

VerifyDesign lets you define the dependencies between software components. Some examples from Figure 2 might be:

  • Implementation of Component A depends on Component B’s API
  • Implementation of Component A depends on Component C’s API
  • Implementation of Component A depends on Component D’s API

In the rest of this article, I will detail how the Verifydesign tool works to define and enforce dependencies.

How Teams use the Verifydesign Tool
Here’s an example system that uses Verifydesign. The system consists of a phone and a client that controls the phone. The client depends on the phone’s API. The phone’s API has no dependencies, and the phone’s implementation must depend only on the phone’s API. Along the way, I’ll present this information as if to a new developer who doesn’t yet understand the design. The code will intentionally violate the design dependency rules to demonstrate Verifydesign’s real power?adherence to an actual design. This desired design is what I call the “package design” (as opposed to the actually implemented design).

First, I urge you to download the code, so you can follow along. The download contains a file named verifydesign/packagedesign/bldfiles/design.xml, shown below:

                 phoneApi   
 
Figure 3. Enforced Relationships: The figure shows a graphical view of the enforced relationships between packages as declared in the design.xml file.
phoneApi

As a first step, run the build and make sure the code you downloaded doesn’t violate the design. To run the build, go to the verifydesign/packagedesign directory and run the command:

   ant --f bldfiles/build.xml 

You should see a BUILD SUCCESSFUL message. The XML file shown above contains some basic rules about dependencies that are allowed in the source code. The file declares five packages. A graphical picture of the above “package design” would look like Figure 3.

The diagram in Figure 3 represents each declared package in the file as a box, with each arrow representing a dependency. These are the only dependencies allowed in the Java code. What we’re trying to achieve is an environment where (for example) when a programmer changes the code such that client depended on PhoneImpl, the developer should be warned. Now, I’ll walk you through Verifydesign’s capabilities in detail. For this demonstration, pretend you don’t know anything about the design, and we’ll wreak some havoc on the system so you can see how VerifyDesign can help.

Finding Unwanted Dependencies
First, I’ll show you what happens if a developer makes one of the most basic mistakes in a system?circumventing a well-defined API. Open up the file verifydesign/packagedesign/input/javasrc/biz/xsoftware/impl/client/Client.java, and add the following code to the method called createPhoneTheWrongWay():

   PhoneImpl phone = new PhoneImpl();
 
Figure 4. Unwanted Dependency Change: According to the design, the client should depend only on the phoneApi. Verifydesign catches this mistake

Figure 4 shows a graphical representation of what was changed in the source code; the red line illustrates the inserted dependency. Note that this represents the implemented design, not the desired “package design” defined in the design file.

After making the change, run the build again, using the same command:

   ant --f bldfiles/build.xml

Instead of the success message from your previous build, you’ll now see the following error:

   You are violating your own design....   Class = biz.xsoftware.impl.client.Client depends on   Class = biz.xsoftware.impl.phone.PhoneImpl   The dependency to allow this is not defined in your design   Package=biz.xsoftware.impl.client is not defined to depend on   Package=biz.xsoftware.impl.phone   Change the code or the design

Notice the last line of the message, “Change the code or the design.” That reflects the reality that the package design always evolves in every software project that exists. This statement is true whether you use Verifydesign or not. Verifydesign has been used on projects with both Agile and Waterfall processes, which have shown that no matter the process, designs always change. Verifydesign simply blatantly points out how designs evolve rather than having such design changes go unnoticed.

Before you continue, remove the breaking change to restore the file to its original state. You should once again be able to build the project successfully.

External Dependencies
Sometimes, you want to isolate an external dependency so that only one package in your source code depends on that external entity. This is very useful if you think you might need to switch the external technology later. By limiting the external dependency to a single package, you ensure that switching entails only rewriting the one dependent package. By default, Verifydesign makes you declare dependencies on anything in javax. Also, by default, it does not require you to declare dependencies on java.*. You can override both these defaults.

Assume that the Phone class depends on java.rmi and all subpackages of java.rmi, including java.rmi.registry. Also assume that you know you will have to change that later, and you don’t want other packages (such as the client) to depend directly on javax.rmi. For example, perhaps you might later want to use a different phone implementation that depends on Web services in addition to the phone implementation that depends on rmi. Or suppose you know in advance that you might eventually want two different Phone implementations.

To enforce this, the first thing you need to do is override the java.* default behavior?but just for java.rmi. You’d make this change to the design.xml file:

                phoneApi       rmi      

The preceding configuration adds a subpackages attribute, telling the design tool that you want to include all subpackages of java.rmi in the rmi package. This means that the PhoneImpl package can now depend on these packages:

  • java.rmi
  • java.rmi.activation
  • java.rmi.dgc
  • java.rmi.registry
  • java.rmi.server

Now, go ahead and add the following code to PhoneImpl.java:

 
Figure 5. Good Encapsulation: Note how the client and phoneApi no nothing about the rmi module. Only the PhoneImpl component (the implementation of the phoneApi) knows about rmi.
   public void makeCall(String number)   {      try      {         Registry registry =              LocateRegistry.createRegistry(5000);      }      catch(RemoteException e)      {         throw new RuntimeException(e);      }   }

A graphical representation of the current “package design” would now look like Figure 5.

This is encapsulation at its best. The client and API will not be allowed to know about RMIExceptions. Note that you cannot throw a RemoteException on the makeCall method, because if you did, you’d have to add the throws RemoteException clause to Phone.java‘s makeCall method in the API package?and that would violate the design by introducing a dependency from the phoneApi to java.rmi.RemoteException. That dependency would then cause the build to fail, because the phoneApi package is not allowed to depend on java.rmi.RemoteException. Remember, only the phone implementation is allowed to depend on java.rmi. The API should not depend on rmi, because you want the client to use an API that is generic and not tied to any specific technology. I leave this for you to try yourself.

Adding New Packages
Now, let’s add a new package called biz.xsoftware.client2, and add a class called Client2.java to that package. This class does not need to have any code in it at all. The real “package design” should now look like Figure 6. Again, note that this is not the defined “package design” in the design file.

 
Figure 6. New Client2 Package: Here’s the dependency situation after adding a second client package.

Now rerun the build using ant?f bldfiles/build.xml and you’ll see the following error.

   Package=biz.xsoftware.client2 is not defined    in the design. All packages with classes must be declared   in the design file. Class found in the offending    package=biz.xsoftware.client2.Client2

Basically, users finds that the “package design” must cover all the code in the module. Verifydesign forces your design to be kept up to date. You can always use the subpackages attribute as shown earlier to include a named group of subpackages. In fact, you could declare all your source code as one big wad, using the default package, which is reserved just for this case:

   

But it’s better to fix the design. Add the following line to your design file.

   

After defining the new package, you should be able to run the build again successfully. Notice that the client2 class has no dependencies, so you don’t have to declare any?but you do have to define the package.

Let’s now add some valid dependencies and change our design to allow those dependencies. First, modify the line of the design file shown earlier to this:

          phoneApi   

Next, copy the Client.java file from the biz.xsoftware.impl.client folder to the biz.xsoftware.client2 folder. This class depends on the PhoneApi package. Now, running the build should pass.

Stale Documentation
Sometimes a developer deletes code from a class that results in a package no longer depending on another package specified in the design. Let’s now do the reverse of what we did previously. Delete the biz.xsoftware.client2.Client.java file, and run the build again. You’ll see the following error:

   Package name=client2 has a dependency declared    that is not true anymore. Please erase the dependency    phoneApi from package=client2

If you follow those instructions and delete the depends node, and then rebuild, the build passes because the code matches the design. Finally, delete the Client2.java file and run the build again.

   Package name=client2 is unused. Full package=biz.xsoftware.client2

At this point, you’ve seen how Verifydesign enforces the dependency restrictions to ensure that your design is always up to date. This feature was recently added to Verifydesign and when we first ran it on our project, we had to delete 17 lines from design.xml, which had around 50 lines at the time. This made reading the “package design” much easier. Just think, you can tell everyone that your design documentation is guaranteed to always be up-to-date.

Wiring Ant-contrib into Your Build
Now, to make sure the tool can enforce dependencies, you have to wire Verifydesign into the builds. The key lines in the verifydesign/package/bldfiles/build.xml file are:

                                    

Those lines load all the ant-contrib tasks. Subsequently, to use Verifydesign on a single jar, just add the following line.

            

You can find more documentation at ant-contrib’s homepage .

Using Verifydesign on Legacy Systems
In many cases, you may have already inherited someone else’s software or are joining a team with an existing product. In most cases, you inherit something like Figure 1. Obviously you would like to clean up the dependencies. You can do that with Verifydesign as well.

In the downloadable code, you can find the code that represents the system in Figure 1 in the verifydesign/legacy/input/javasrc folder.

You’d start the process by defining a design file that allows the build to pass. Don’t worry about APIs at this point. Here’s the first design file you might write (see the file verifydesign/legacy/bldfiles/design.xml in the downloadable code):

                                    
 
Figure 7. Cleaning Up: The figure illustrates the dependency that needs to be cleaned up.

The needdepends=”false” attribute in the preceding code provides a way to specify that your package does not need to declare any dependencies. Similarly the needdeclarations=”false” attribute specifies that dependent packages don’t have to declare their dependencies either. These attributes are particularly useful for javax.swing so you can enter needdeclarations=”false” once, and avoid writing swing on every single component that depends on swing. Now, go to the verifydesign/legacy directory and run the command below to make sure that the build succeeds:

   ant --f bldfiles/build.xml

On to the next step. In a legacy system, the business goal is to keep delivering business value while “slowly” cleaning up the design. For example, in release one, you might target one dependency you don’t like and clean that up. In Figure 7, the red arrow highlights the dependency to be removed first

To do that, you need to find all the classes that cause Component E to depend on Component C. I’ve removed the needdepends attribute from componentE in the preceding code.

                                     

At this point, you’ll need to declare everything componentE depends on or the build will break. In this case, because all the other packages have needdeclarations set to false, the build will break only if componentE depends on external packages. You can now run the build command ant?f bldfiles/build.xml on the legacy system and see what happens.

You’ll find that Component E depends on something in Swing. That’s fine, just add a new package for Swing and allow everyone to depend on it (which may or may not be a good idea depending on your system). Add this line to the top of the design.xml file.

   

With this new line, the build should now succeed again. You can run the build and verify. Now the external dependencies that the componentE package depends on are resolved, and you can move on to the next package, using the same techniques to discover anything it depends on in the componentC package. Remove the needdeclarations attribute from componentC, forcing components without the needdepends attribute to define any dependencies on componentC. Here’s the new design file with changes in bold.

                               

Now anything that depends on componentC must declare that they do, except for components with needdepends=”false”?which for right now is all of them except Component E. Run the build again and the error will be

   You are violating your own design....   Class = biz.xsoftware.componentE.SubComponentE depends on   Class = biz.xsoftware.componentC.ComponentC   The dependency to allow this is not defined in your design   Package=biz.xsoftware.componentE is not defined to depend on   Package=biz.xsoftware.componentC   Change the code or the design

Based on that error message, you can go to the SubComponentE class, which depends on the ComponentC class and fix the dependency in the code. At this point, your code now adheres to the new design. This demonstration legacy project contained only one dependency tying ComponentE to ComponentC, but in a real system, you’ll probably need to clean up many, many dependencies. Verifydesign will tell you all of them.

Fixing the “componentE depends on componentC” problem is left up to you. I will leave you with a hint though. Think about using a listener in componentE and having componentC’s classes implement that listener. A discussion of using design patterns to reverse dependencies would require a complete article by itself, so I will not go into more detail on that subject here.

One Caveat
There is one small caveat with Verifydesign I have not mentioned. Verifydesign cannot check dependencies on constants. For example, suppose you have two classes:

   biz.xsoftware.impl.client.Client   biz.xsoftware.impl.phone.PhoneImpl

Furthermore, suppose Client.java depends on a field in PhoneImpl.java defined as follows:

   public static final String someConstant = "xyz";

When you compile Client.java, the bytecode will contain “xyz” and will give no indication that Client depended on phoneImpl. And because Verifydesign analyzes bytecode to check out all the dependencies, it cannot catch this mistake. With all other dependencies phoneImpl would have been referenced by Client.class in the bytecode such that Verifydesign could flag the violation of the design.. Unfortunately, only Sun can fix this problem?by adding to the Java Language Specification. This problem does occur once in a while, but even without Verifydesign’s help, these are the easiest of all dependencies to correct. Overall, VerifyDesign does a great job of guaranteeing that your code follows the “package design” you want.

You’ve now seen an overview of Enterprise Architecture Preservation (EAP) in action assisted by the Verifydesign tool. You have seen how a team might use it, and how to deploy it on a legacy system. It’s often surprising to see all the dependencies that have crept in when you deploy this tool on a legacy system. Typically, developers find Verifydesign annoying to use at first, but after a few weeks of use, you start to really see the value. It catches unwanted dependencies at every step. These unwanted package dependencies are your package design so Verifydesign forces you to think about how code changes might change that design. Maybe someone, someday, will create an Ant task to generate a slick graphical picture of the design from the design file, or integrate visual design into our favorite IDEs.

Naturally Verifydesign sets up an architecture so its much easier to start putting unit tests around the components themselves. A library called mocklib was written specifically to be compatible with Verifydesign. Mocklib has the ability to simulate entire APIs, meaning it can simulate componentE’s API to test componentC. Then, you can completely refactor componentC without rewriting a single test in the test suite. Test Suites change only when the component APIs change. Feel free to check that project out as well.

To close, here are a few links to projects that currently use Verifydesign. You can download these open source projects and look at their “package designs.” Note that the Java Sip project has two layers; you can grab the bottom layer and be guaranteed you won’t have to take all the other code, which is quite nice. Interestingly, all the projects below also use mocklib. The javasip project uses mocklib to simulate the entire channelmanager API (not just a single interface). Also check out the Java State Machine project, and the NIO Abstraction project.

Share the Post:
Share on facebook
Share on twitter
Share on linkedin

Overview

Recent Articles: