Module Reuse

by kirk knoernschild

Statement

Emphasize reuse at the module level.

Description

One of the oft cited benefits of object oriented development is reuse. Mostly, we’ve failed miserably in achieving any degree of reuse that is directly attributable to objects. A large part of this failure is very likely because using objects does not guarantee reuse, but actually requires a significant amount of thought, effort, and usually refactoring to make reuse happen. As I’ve witnessed it though, the heart of the problem is identifiable.

The object paradigm introduced many new ideas to the development community, but none were so revolutionary as polymorphism and inheritance. Giving developers these two powerful language constructs created a new way of thinking about, and designing, software applications. Given that the advantage of object oriented development was higher degrees of reuse, there was a natural tendency to believe that it was these two new constructs that would enable, and virtually guarantee, reuse. Nothing is further from the truth, however. Instead, these two powerful constructs have little to do with facilitating reuse directly. In the last few years, we’ve actually found that ImplementationInheritance hinders reusability more so than facilitating reuse by locking a developer into a rigid design with little ability to meet the changing needs of most complex software systems. In fact, structured programming techniques, which have been around for more than 30 years, can be used to achieve reasonable degrees of reuse. I’ve witnessed many situations where legacy COBOL code that was well-written could easily be reused.

So, to start, we need to break away from the thinking that objects help us create more reusable software. Instead, objects help us create more extensible software, which is an enabler of reuse. Using inheritance and polymorphism allows us to create designs that are more flexible. It’s this flexibility, and the ability to plug different implementations into a class referencing an abstraction, that allows us to create generic software that can be configured to meet our needs. The ability to slightly modify the behavior of a chunk of code typically results in more reusable code. As a guideline, the less something does, the more reusable it will be. The trick, of course, is to make something general and configurable enough so that it’s reusable in a variety of contexts, while also making it do enough so that it’s useful. Given that extensibility and configurability are indirect enablers of reuse, it’s much more common to reuse a set of collaborating classes over an invidual class. We discussed this tension between use and reuse in Chapter 6.

Herein lies one of the greatesst problems with achieving reusability using object oriented development. The class, a combination of attributes and behaviors, which is commonly the focal point of most object oriented designers, is not an adequate reuse construct. Reuse is a matter of scale, and attempting reuse at the class level is to small scale to realistically expect that high degrees of reuse can be achieved.

Of course, there are other factors that contribute to our inability to achieve higher degrees of reuse. Many highly touted design techniques, such as the Unified Modeling Language (UML) and patterns, tend to draw our attention toward class design. Attempts to use the design techniques to create robust designs before coding only excacerbates the problem, and for large systems with many classes and many developers the problem grows even worse. Instead of attempting to reuse at the class level, we should turn our attention to using the object oriented concepts to design more flexible class relationships.

Of course, most of us have experienced some degree of success with class level reuse, especially within an application. Utility classes are common in most applications, and are generally reusable. But even these utility classes can be difficult to reuse across applications . To reuse within an application, all classes can be bundled together. But to reuse across applications, classes need to be bundled separately into cohesive units. If dependencies between these individual units are not  managed carefully, reuse will not follow. A failure to bundle your code correctly is one of the leading causes for the most common form of reuse – copy and paste. To effectively achieve higher degrees of reuse, we need a higher level unit of modularity around which code can be organized, bundled, and deployed. This higher level unit of modularity is the component, or in Java, the JAR file. The reuse release equivalence principles states this a bit more succinctly:

The unit of reuse is the unit of release.

Instead of emphasizing reuse at the class level, we should emphasize reuse at the module level. Then, using object oriented design concepts, we create flexible clss designs that are extensible, configurable, and exhibit low degrees of coupling. We bundle these classes into cohesive and independently deployable modules that can then be easily reused. A flexible class design allows you to refactor your modules by moving classes to different modules based on need.

The most compelling evidence suggesting that we should emphasize reuse at the module level is through examination of the open source community and many of its frameworks. The most widely adopted and successful open source products are JAR files that can be easily incorporated into your application. The most flexible of these offerings provide hooks into the framework that allow you to customize it to your need. But all emphasize the module as the unit of reuse.

Implementation Variations

Horizontal modules span business domains, and do not contain functionality that is industry specific. It’s much easier to develop horizontal modules for reuse because their usage pattern is consistent and well-known. Most open source software, such as Struts, Hibernate, and Spring fall into this category. Vertical modules, on the other hand, are specific to a business domain. They offer behavior rich with functionality specific to your organization. Many of the modules developed for business software fall into this category.

Horizontal modules are much easier to develop than vertical modules. The challenge with vertical modules is that you are forced to predict the requirements of future usage scenarios, which is an impossible task. For instance, Hibernate, a widely used persistence framework, allows you to bridge the object and relational world regardless of which backend relational database you are using. Attempting to develop an Employee object that  is reused across the enterprise is mostly impossible since different applications within your organization will require different behaviors of the Employee.

Since vertical modules are so much more difficult to reuse, it’s important to create these modules with the least amount of functionality that is common to all applications that will use it. Therefore, you have to be very cautious in adding any degree of rich behavior. A good starting point is a modules that facades a backend datasource, and returns raw business objects with little functionality. These business objects, while offering little functionality, contain the data and other methods that allow the object to be configured to it’s context.

There are two main impediments you’ll encounter when trying to reuse modules. First, the module doesn’t offer the right level of functionality that you need, and you cannot customize the modules to fit your conext. This is typically a problem when the module has not been created at the correct level of granularity. The module does more than what you want. When developing modules, it’s important to only offer functionality that you know is appropriate. If you question whether the module should do something extra, you should either avoid putting the behavior in the module, or offer the ability to configure the module so that the behavior can be turned off. Second, you may find that amodulehas too many heavyweight dependencies on something else, thus preventing you from reusing it because you do not want those heavyweight dependencies. If you SeparateAbstractions and provide ExternalConfiguration, you’ll likely be able to remove some of the unwarranted dependencies as well as allow the component to be more easily configured to context.

Services differ from modules in that services typically span distribution boundaries whereas modules are invoked in-process. For years, industry luminaries have predicted that services will revolutionize software reuse. I’m not convinced that this is true. Services along will not provide the revolutionary boost to software architecture that’s necessary. It will be just as easy to create heavyweight and unusable services as it is to create heavyweight and unusable components. In Chapter 4, we discussed the concept of architecture all the way down. Truly great applications are going to require a solid design from the class structure, to the module structure, and finally to the service level. If you create a robust suite of modules, that are loosely coupled and wired together at run-time, these modules can be used to compose your services.

Module management can be difficult. A large number of modules with multiple versions of each make it difficult for others to ensure they are using the correct version of a modules. A repository in your version control software is a great places to store the binaries of your modules. An application’s build process can be configured to pull the correct version of the module from the repository during each build.

NOTE ON OSGI AND SUPPORT FOR MULTIPLE VERSIONS HERE.

Consequences

Emphasizing reuse at the component level offers a much greater probability that you’ll succeed in your reuse efforts. However, the pipe dream of assembling applications from pre-built components will likely never come true. Organizational change will always dictate that software be customized and it’s unlikely that organizations within an industry will standardize their processes since this standardization eliminates a company’s competitive advantage.  However, a suite of robust components will help an organization deliver software more quickly. The ability to assemble these components, add to them, and build custom software on top of them will help an organization achieve and sustain their competitive advantage.

Maximizing the reuse of a component usually means creating smaller components that do less. This can make component management a difficult task. Contrarily, larger components that do more will be easier to manage, but may not be as reusable. It can be very difficult to balance these two competing agendas. I have found that early in the development lifecycle, while the software is still in a very dynamic state, larger components tend to be easier to manage. As distinct areas of reuse are identified, these larger components can be broken out into smaller, more reusable components. Attempting to define smaller fine-grained components earlier in the lifecycle is difficult since you are forced to guess the functionality of a component. This is usually a lost cause.

Sample Code

An insurance company receives policy applications that are scanned into a system. The scanned copy of the image is stored in an enterprise document management system. Optical Character Recognition (OCR) software extracts data from the scanned image and stores the data to an operational application database. Before storing the data to the database, the data must pass through a series of business rule validations, and the policy is flagged with a status based upon the outcome of the business rules.

A separate web application is used by the Underwriting department to review the policy information, cleanse any data incorrectly interpreted by the OCR software, and ultimatly allow the Underwriter to assess the insurability of the customer. Any changes to the policy information must pass through the same set of business rule validations. However, if any of the validation rules fail, instead of setting a flag on the policy, the Underwriter is prompted to correct the information.

In this scenario, we have two independent systems that must share some common validation rules. One approach is to develop a distributed service that is deployed once and invoked by each of the applications. In lieu of incurring the performance hit of a distributed call, we’ll opt to develop a component that helps us validate the policy information. To do this, however, we must ensure that the component does not have any application dependencies. Of course, we’d have to do the same for a service, but more on that a bit later.

The OCR software sends the data feed in XML format. Our initial pass at validating the information leads us to creating a Policy class. The constructor of our Policy class accepts the XML String and parses it out to build the Policy object. The validate method can then be called to ensure the data is correct before calling the save method to store the data to the application database.

package com.extensiblejava.policy;

import java.util.*;
import java.io.*;
import javax.xml.parsers.*;
import org.xml.sax.helpers.*;
import org.xml.sax.*;

public class Policy {

   private String firstName;
   private String lastName;
   private String tobaccoUser;
   private Date dateOfBirth;
   private String maritalStatus;

   public Policy(String xmlString) {
      try {
         SAXParserFactory factory = SAXParserFactory.newInstance();
         SAXParser parser = factory.newSAXParser();
         InputSource source = new InputSource(new StringBufferInputStream(xmlString));
         parser.parse(source, new PolicyDefaultHandler(this));
      } catch (Exception e) {
         e.printStackTrace();
      }
   }
   void setFirstName(String firstName) { this.firstName = firstName; }
   void setLastName(String lastName) { this.lastName = lastName; }
   void setTobaccoUser(String tobaccoUser) { this.tobaccoUser = tobaccoUser; }
   void setDateOfBirth(Date dateOfBirth) { this.dateOfBirth = dateOfBirth; }
   void setMaritalStatus(String maritalStatus) { this.maritalStatus = maritalStatus; }
   public String getFirstName() { return this.firstName; }
   public Date getDateOfBirth() { return this.dateOfBirth; }
   public String getLastName() { return this.lastName; }
   public String getMaritalStatus() { return this.maritalStatus; }
   public String getTobaccoUser() { return this.tobaccoUser; }
   public void validate() {
      //validate the data.
   }
   public void save() {
      //save the data.
   }
}

Listing 1: The Policy Class

The PolicyDefaultHandler, in the same module as policy, sets the individual attributes on the Policy object as the XML string is parsed.

package com.extensiblejava.policy;

import java.util.*;
import org.xml.sax.helpers.*;
import org.xml.sax.*;

class PolicyDefaultHandler extends DefaultHandler {
   private Policy policy;
   private String attribute;
   public PolicyDefaultHandler(Policy policy) {
      this.policy = policy;
   }
   public void characters(char[] ch, int start, int length) {
      String element = new String(ch, start, length);
      this.setPolicyAttribute(element);
   }
   public void startElement(String uri, String localName, String qName, Attributes attributes) {
      this.attribute = qName;
   }
   private void setPolicyAttribute(String value) {
      if (this.attribute.equals("firstname")) {
         policy.setFirstName(value);
      } else if (this.attribute.equals("lastname")) {
         policy.setLastName(value);
      } else if (this.attribute.equals("dateofbirth")) {
         Calendar cal = Calendar.getInstance();
         Integer month = new Integer(value.substring(0,2));
         Integer day = new Integer(value.substring(3,5));
         Integer year = new Integer(value.substring(6,10));
         //System.out.println(value.substring(0,2) + "  " + value.substring(3, 5) + "  " + value.substring(6, 10));
         cal.set(year.intValue(), month.intValue() - 1, day.intValue());
         policy.setDateOfBirth(cal.getTime());
      } else if (this.attribute.equals("tobaccouser")) {
         policy.setTobaccoUser(value);
      } else if (this.attribute.equals("maritalstatus")) {
         policy.setMaritalStatus(value);
      }
   }
}

Listing 2: The PolicyDefaultHandler Class

The result is a valid Policy object that can now be validated and saved to the database. The above approach works well for the OCR and scanning application. However, the Policy is tightly coupled to the XML and cannot be used in our Underwriting application. For the validation rules and persistence behavior on our Policy class to be reusable in another application, we must not only decouple our Policy class from this XML String, but we must also ensure that the Policy class can be deployed independent of the XML behavior. In the situation above, because our Policy class is so heavily dependent on the XML parsing functionality, the Policy behavior we want to reuse cannot be deployed separately. Intead, a single policy.jar module is our only deployable unit, as shown in Figure 1.

ModuleReusePolicy1

Figure 1: The Policy Module

To fix the problem with Policy above, we have to physically separate the process of constructing the Policy object from the format of the data used to construct Policy. To achieve this separation, our Policy class has been refactored to accept an abstract PolicyBuilder to it’s static buildPolicy method.

package com.extensiblejava.policy;

import java.util.*;
public class Policy {
   private String firstName;
   private String lastName;
   private String tobaccoUser;
   private Date dateOfBirth;
   private String maritalStatus;
   public static Policy buildPolicy(PolicyBuilder policyBuilder) {
      return policyBuilder.build();
   }
   public Policy(String firstName, String lastName, String tobaccoUser, Date dateOfBirth, String maritalStatus) {
      this.firstName = firstName;
      this.lastName= lastName;
      this.tobaccoUser = tobaccoUser;
      this.dateOfBirth = dateOfBirth;
      this.maritalStatus = maritalStatus;
   }
   public String getFirstName() { return this.firstName; }
   public Date getDateOfBirth() { return this.dateOfBirth; }
   public String getLastName() { return this.lastName; }
   public String getMaritalStatus() { return this.maritalStatus; }
   public String getTobaccoUser() { return this.tobaccoUser; }
   public void validate() {
      //validate the data.
   }
   public void save() {
      //save the data.
   }
}

Listing 3: Decoupled Policy Class

The Policy class has now been decoupled from the construction of the Policy object using XML as the input source. The PolicyBuilder interface defines the build method that the implementing class must provide to construct the Policy instance.

package com.extensiblejava.policy;
public interface PolicyBuilder {
   public Policy build();
}

Listing 4: PolicyBuilder Class

We now define a PolicyXMLBuilder implementation, which we’ll put in a separate module that will build the Policy instance.

package com.extensiblejava.builder.xml;

import java.util.*;
import java.io.*;
import javax.xml.parsers.*;
import org.xml.sax.helpers.*;
import org.xml.sax.*;
import com.extensiblejava.policy.*;
public class PolicyXMLBuilder implements PolicyBuilder {
   private String xmlString;
   private String firstName;
   private String lastName;
   private String tobaccoUser;
   private Date dateOfBirth;
   private String maritalStatus;
   public PolicyXMLBuilder(String xmlString) {
      this.xmlString = xmlString;
   }
   public Policy build() {
      try {
         SAXParserFactory factory = SAXParserFactory.newInstance();
         SAXParser parser = factory.newSAXParser();
         InputSource source = new InputSource(new StringBufferInputStream(xmlString));
         parser.parse(source, new PolicyDefaultHandler(this));
      } catch (Exception e) {
         e.printStackTrace();
      }
      return new Policy(this.firstName, this.lastName, this.tobaccoUser, this.dateOfBirth, this.maritalStatus);\
   }
   void setFirstName(String firstName) { this.firstName = firstName; }
   void setLastName(String lastName) { this.lastName = lastName; }
   void setTobaccoUser(String tobaccoUser) { this.tobaccoUser = tobaccoUser; }
   void setDateOfBirth(Date dateOfBirth) { this.dateOfBirth = dateOfBirth; }
   void setMaritalStatus(String maritalStatus) { this.maritalStatus = maritalStatus; }
}

Listing 5: PolicyXMLBuilder Class

Finally, a simple integration test proves correctness.

</pre>
package com.extensiblejava.policy.test;

import junit.framework.*;
import junit.textui.*;
import com.extensiblejava.policy.*;
import com.extensiblejava.builder.xml.*;
import java.util.*;
import java.io.*;
public class PolicyXMLTest extends TestCase {
   public static void main(String[] args) {
      String[] testCaseName = { PolicyXMLTest.class.getName() };
      junit.textui.TestRunner.main(testCaseName);
   }
   protected void setUp() {}
   public void testPolicy() throws Exception {
      String policyXML = ""+"Jane"+"Doe"+"M"+"01/10/1967"+"N"+"";
      Policy policy = Policy.buildPolicy(new PolicyXMLBuilder(policyXML));
      assertEquals("Jane", policy.getFirstName());
      assertEquals("Doe", policy.getLastName());
      assertEquals("M", policy.getMaritalStatus());
      assertEquals("N", policy.getTobaccoUser());
      Calendar cal = Calendar.getInstance();
      cal.setTime(policy.getDateOfBirth());
      assertEquals(10, cal.get(Calendar.DAY_OF_MONTH));
      assertEquals(1967, cal.get(Calendar.YEAR));
      assertEquals(1, cal.get(Calendar.MONTH) + 1);
   }
}

Listing 6: PolicyXMLTest Class

Since the construction of the Policy class has now been separated out into a separate module, we have the ability to deploy the Policy behavior independent of the XML construction process. The new module structure can be seen in Figure 1.

ModuleReusePolicy

Figure 2: Policy Module Diagram

Since the functionality to validate and save policy information has been separated from the XML construction process, the policy.jar module can now be deployed independent of the policyxml.jar module. Defining a new implementation of PolicyBuilder in the Underwriting application should allow us to construct the Policy instance from the data retrieved from the application database.

This resulting structure offers some significant advantages. Through the example, we’ve already illustrated how the policy functionality can be reused across applications. In the future, if we decide to deploy the policy functionality as a service invoked via a distributed protocol, such as EJB or JMS, the modular approach we’ve taken will help faciliate that endeavor.

Wrapping Up

The Reuse Release Equivalence Principle implies that software is reused at the same unit of functionality at which is deployed. In Chapter 2, we defined a software module and highlighted that a module is a unit of deployment and a unit of intra-process reuse. By definition, modules are the only software entity that match this set of criteria and designing good software modules presents development teams another option for how they choose to reuse software entities.