JaVa
   

Abstract Persistence Interface

Consider a small app to maintain customer relationships, a tiny customer relationship management (CRM) system. The persistent object model of this example system has stabilized in the course of the first few iterations to the structure shown in Screenshot. In the center is the customer (Customer), who is assigned to exactly one category (CustomerCategory), and who can be contacted an arbitrary number of times (CustomerContact).

Java ScreenShot
Screenshot: Object model of the CRM app.

A naive testing approach handles persistent objects like all other objects and uses a database like an internal resource. Opting for this approach often means that we will have a persistence interface that is either static or implemented as a singleton. The following static database class is conceivable for the objects represented in Screenshot:

public class CRMDatabase {
 public static void initialize(String dbURL)
 throws CRMException {...}
 public static void shutdown() throws CRMException {...}
 public static CustomerCategory createCategory(String name)
 throws CRMException {...}
 public static void deleteCategory(CustomerCategory category)
 throws CRMException {...}
 public static Set allCategories() throws CRMException {...}
 public static Customer createCustomer(String name,
 CustomerCategory category) throws CRMException {...}
 public static void writeCustomer(Customer customer)
 throws CRMException {...}
 public static void deleteCustomer(Customer customer)
 throws CRMException {...}
 public static Customer getCustomer(String id)
 throws CRMException {...}
 public Set allCustomers(CustomerCategory category)
 throws CRMException {...}
}


There are methods for initializing and stopping the database and for creating, writing, deleting, and retrieving categories and customers. CustomerContact instances depend on customer objects and are stored, written, and deleted through these objects. One of the important tasks of this CRM system is to create regular reports. For example, a daily report (DailyReport) determines the number of customer contacts with specific customer categories. The following piece of code was taken from the pertaining test class:

public class DailyReportTest extends TestCase {
 private DailyReport report;
 private Calendar reportDate;
 private CustomerCategory catFortune100,catSmallCompany;
 private final String DB_URL = "jdbc:odbc:CRM";
 private List customersToDelete = new ArrayList();
 protected void setUp() throws Exception {
 reportDate = Calendar.getInstance();
 report = new DailyReport(reportDate);
 CRMDatabase.initialize(DB_URL);
 catFortune100 =
 CRMDatabase.createCategory("Fortune 100");
 catSmallCompany =
 CRMDatabase.createCategory("small company");
 }
 protected void tearDown() throws Exception {
 Iterator i = customersToDelete.iterator();
 while (i.hasNext()) {
 Customer each = (Customer) i.next();
 CRMDatabase.deleteCustomer(each);
 }
 CRMDatabase.deleteCategory(catFortune100);
 CRMDatabase.deleteCategory(catSmallCompany);
 CRMDatabase.shutdown();
 }
 public void testAllContacts() throws Exception {
 Customer customer1 = CRMDatabase.createCustomer(
 "Customer 1", catFortune100);
 customersToDelete.add(customer1);
 Customer customer2 = ...
 Customer customer3 = ...
 Calendar dayBefore = (Calendar) reportDate.clone();
 dayBefore.add(Calendar.DATE, -1);
 customer1.addContact(dayBefore, "note 1");
 customer1.addContact(reportDate, "note 2");
 CRMDatabase.writeCustomer(customer1);
 customer2.addContact(reportDate, "note 3");
 CRMDatabase.writeCustomer(customer2);
 customer3.addContact(dayBefore, "note 4");
 CRMDatabase.writeCustomer(customer2);
 List contacts =
 report.allContactsForCategory(catFortune100);
 assertEquals(1, contacts.size());
 // ...
 }
}


The following problems emerge:

Again, the Dependency Inversion Principle (see , ) offers a way out of this tricky test situation. In the above code example, the DailyReport class depends directly upon the database interface. This violates the rule stating that high-level modules should not depend on lower-level modules. We break this dependence by encapsulating persistence in an abstract interface:

public interface CRMPersistence {
 void shutdown() throws CRMException;
 CustomerCategory createCategory(String name)
 throws CRMException;
 void deleteCategory(CustomerCategory category)
 throws CRMException;
 Set allCategories() throws CRMException;
 Customer createCustomer(String name,
 CustomerCategory category) throws CRMException;
 void writeCustomer(Customer customer) throws CRMException;
 void deleteCustomer(Customer customer) throws CRMException;
 Customer getCustomer(String id) throws CRMException;
 Set allCustomers(CustomerCategory category)
 throws CRMException;
}


All currently static methods of the CRMDatabase class are now found in CRMPersistence, with one exception, initialize(String url) is an implementation detail so that it has no business in the abstract interface.


JaVa
   
Comments