Database TDD Part 8: Zealotry

Some people purport that you can keep the “cost of change” curve relatively flat. In other words, you can introduce unplanned features any time in the future as cheaply as if you had inserted them today. That’s a bold statement. I agree with it if and only if you refactor with extreme vigilance.

To be successful, you will need to become a refactoring zealot. This means you learn to understand what duplication really means, and ferret it out at every possible opportunity. Every TDD cycle includes writing test code, writing production code, then refactoring. Any new code introduced in the first two parts of the cycle must not degrade the system’s current clean design. Otherwise you cannot claim that the cycle is complete.

With the 10-minute bit of code I wrote to support saving and retrieving users, I’ve already spotted gobs of duplication and expressiveness problems. As I refactor more, I seem to spot even more. It’s like clearing the underbrush beneath trees in your yard. Picking up the larger branches on top alerts you to the presence of the smaller branches snaking through the grass. They too have to be cleaned up if you want to keep your grass looking pristine.

Yesterday I mentioned my desire to use static import. That was my first subject of refactoring today. I moved all of the classes into appropriate packages. I even renamed a few classes (plus one method), and at least one class I renamed more than once! I initially moved JdbcAccess into a package called jdbc, then renamed it to Access (to remove the redundancy between the class name and the package). But then I figured I’d be better off with the generic package name persistence, so I renamed the class back to JdbcAccess. A bit of unfortunate thrashing, indeed, but it was a cheap exercise. The beauty of Eclipse and other IDEs is that you don’t have to have a perfect name first. You can continually improve identifier names as you discover more about what they represent. Very powerful! This makes the third simple design rule (code should be expressive, basically) easy and even fun to tackle.

The new project organization:

  • domain.User
  • domain.UserTest
  • persistence.JdbcAccess
  • persistence.JdbcAccessTest
  • persistence.JdbcAccessExceptionsTest
  • persistence.JdbcException
  • util.StringUtil
  • util.StringUtilCommaDelimitTest

I still don’t like some of these class names. Suggestions?

So now, I could use the static import facility:

package util;

import junit.framework.*;
import static util.StringUtil.commaDelimit;

public class StringUtilCommaDelimitTest extends TestCase {
   public void testDegenerate() {
      assertEquals("", commaDelimit(new String[] {}));
   }

   public void testOneEntry() {
      assertEquals("a", commaDelimit(new String[] {"a"}));
   }

   public void testTwoEntries() {
      assertEquals("a,b", commaDelimit(new String[] {"a", "b"}));
   }

   public void testEmbeddedCommas() {
      // not very well defined yet! don't care so far.
      assertEquals("a,,,b", commaDelimit(new String[] {"a,", ",b"}));
   }
}

I think this is a perfect use of the (relatively) new J2SE 5.0 feature. Eclipse italicizes the method call, so it’s clear that it’s a static method.

The next part of today’s work (I was only up to about 3 minutes worth of refactoring at this point) was eliminating the duplication inherent in putting together a value list for the insert statement. Step one was to do an extract method refactoring to make it clear what was getting cleaned up. In User:

   ...
   public void save() {
      new JdbcAccess().execute("insert into " + TABLE_NAME + " ("
            + User.createColumnList() + ") values (" +
             createValuesList() + ")");
   }

   private String createValuesList() {
      return String.format("'%s', '%s'", name, password);
   }
   ...

Step two: enhance my string utility method, looking for duplication along the way. There was lots of it. Here’s what I ended up with:

User.java

   public void save() {
      new JdbcAccess().execute("insert into " + TABLE_NAME + " ("
            + User.createColumnList() + ") values (" +
             createValuesList() + ")");
   }

   private String createValuesList() {
      Transformer ticWrapper = new Transformer() {
         public String transform(String input) {
            return StringUtil.wrap(input, '\'');
         }};
      return StringUtil.commaDelimit(new String[] { name, password }, ticWrapper);
   }

   private static String createColumnList() {
      return StringUtil.commaDelimit(columns);
   }

StringUtilCommaDelimitTest.java

package util;

import junit.framework.*;
import static util.StringUtil.commaDelimit;

public class StringUtilCommaDelimitTest extends TestCase {
   ...
   public void testCommaDelimitWithCallback() {
      Transformer duplicator = new Transformer() {
         public String transform(String input) {
            return input + input;
         }};
      assertEquals("aa,bb", commaDelimit(new String[] {"a", "b"}, duplicator));
   }
}

Transformer.java

package util;

public interface Transformer {
   String transform(String input);
}

StringUtilWrapTest.java

package util;

import junit.framework.*;
import static util.StringUtil.wrap;

public class StringUtilWrapTest extends TestCase {
   public void testDegenerate() {
      assertNull(wrap(null, '\''));
   }

   public void testSingleCharacterString() {
      assertEquals("*a*", wrap("a", '*'));
   }

   public void testMultipleCharacterString() {
      assertEquals("-abc-", StringUtil.wrap("abc", '-'));
   }
}

StringUtil.java

package util;


public class StringUtil {
   public static String commaDelimit(String[] strings, Transformer transformer) {
      StringBuilder builder = new StringBuilder();
      for (int i = 0; i < strings.length; i++) { 
         if (i > 0)
            builder.append(',');
         builder.append(transformer.transform(strings[i]));
      }
      return builder.toString();
   }

   public static String commaDelimit(String[] strings) {
      final Transformer nullTransform = new Transformer() {
         public String transform(String input) {
            return input;
         }
      };
      return commaDelimit(strings, nullTransform);
   }

   public static String wrap(String source, char wrapper) {
      if (source == null)
         return null;
      StringBuilder builder = new StringBuilder();
      builder.append(wrapper);
      builder.append(source);
      builder.append(wrapper);
      return builder.toString();
   }
}

Interestingly, eliminating duplication has meant more code up to this point.

Also, it’s inevitable that you’ll have duplication at some level. Here it exists in the wrap method. I’ve chosen to foist the duplication from a volatile area (code in User) to the most abstract level possible, this wrap method. I don’t see this method as ever having to change.

There’s still SQL-related code in the domain class. That might be my next step, unless someone has another thought.

Original Comments:

 

You are using StringBuilders a lot in these exercises. Given that they are a lot less readable than string concatenation, it seems like a performance optimization to me. In point of fact, the implementation of the wrap method should be just as efficient written as

return wrapper + string + wrapper;

because of compiler optimization.

sorry, that should have been

return string == null ? null : wrapper + string + wrapper;

===

I prefer the guard clause when it comes to something like this. While I don’t mind using the ternary operator at times, I’m picky about when I introduce it. Here I think it just makes things a bit more confusing.

 

Database TDD Part 7: Listening to Your Pair

I had a different direction in mind for my next increment on the database code. A comment by Jeff B. on the last blog acted as a prod from my virtual pair. When there are a lot of alternatives as to where to proceed next, as there are in this case, I’m more than happy to take a suggestion and run with it.

Jeff suggested something I had in mind but wasn’t going to worry about for a little while. The User class contains a method that creates a column list for an SQL statement. Really, this is a generic string method that concatenates a bunch of strings into a comma-separated list. As such, it’s better suited to being refactored to a common string utility class. That will highlight its availability to other parties on the team, increasing its likelihood of being reused.

This time, instead of refactoring the code to a new class in the absence of tests, I chose to construct the string utility using tests as my driver. I wrote a more complete gamut of tests, including the degenerate case (no strings in the list), one string in the list, and multiple strings in the list. Each time, where appropriate, I simply copied code over from the source method (createColumnList) in the User class. Here’s the new StringUtilTest and StringUtil classes:

import junit.framework.*;

public class StringUtilCommaSeparateTest extends TestCase {
   public void testDegenerate() {
      assertEquals("", StringUtil.commaSeparate(new String[] {}));
   }

   public void testOneEntry() {
      assertEquals("a", StringUtil.commaSeparate(new String[] {"a"}));
   }

   public void testTwoEntries() {
      assertEquals("a,b", StringUtil.commaSeparate(new String[] {"a", "b"}));
   }

   public void testEmbeddedCommas() {
      // not very well defined yet! don't care so far.
      assertEquals("a,,,b", StringUtil.commaSeparate(new String[] {"a,", ",b"}));
   }
}
public class StringUtil {
   public static String commaSeparate(String[] strings) {
      StringBuilder builder = new StringBuilder();
      for (int i = 0; i < strings.length; i++) {
         if (i > 0)
            builder.append(',');
         builder.append(strings[i]);
      }
      return builder.toString();
   }
}

You’ll note that there’s an additional test, testEmbeddedCommas. This test passed automatically. It’s there as a placeholder. I know that I don’t explicitly handle embedded commas, and that the current definition is less than desirable. But I did think of this possibility. Rather than define something that might not be the correct definition, I chose to leave the implementation as is. The test is there as documentation that the definition may need to be reconsidered in the future.

The test class name isn’t simply “StringUtilTest.” I didn’t want one single test method for commaSeparate with all four asserts in them. I also wanted to avoid the duplication in naming four tests “testCommaSeparateOneEntry,” “testCommaSeparateMultipleEntries,” etc. So I chose to instead recognize these four tests as belonging to their own separate test class (fixture). Future StringUtil tests would go in another fixture. (Begs the question, though, why not just name the class CommaSeparator?)

Testing static methods introduces another form of duplication nuisance. Having to scope the static method call with the class name each time seems unnecessary–particularly if all tests for a single static method appear in one fixture, and the scope is clear. J2SE 5.0 gives you the option of using static import. I don’t recommend the use of static import in many cases, but this is one of the rare cases where its use doesn’t obscure things.

Unfortunately, I just found out that you can’t statically import a class from the default package! Or if you can, I can’t figure out how (any help?). So my next increment will probably involve moving all classes into explicit packages, and then applying the static import.

The final piece of refactoring for the day is the fun part–eliminating the code from User. Since the createColumnList method is called from two places, I chose to keep this method in place, and have it delegate to StringUtil.

import java.util.*;

public class User {
   ...
   private static String[] columns = { "name", "password" };
   ...
   public void save() {
      new JdbcAccess().execute(String.format("insert into " + TABLE_NAME + " ("
            + User.createColumnList() + ") values ('%s', '%s')", name, password));
   }

   private static String createColumnList() {
      return StringUtil.commaSeparate(columns);
   }

   public static User find(String nameKey) {
      JdbcAccess access = new JdbcAccess();
      List<String> row = access.executeQuery(String.format(
            "select " + createColumnList() + " from " + TABLE_NAME + " where name = '%s'",
            nameKey));
      return new User(row.get(0), row.get(1));
   }
}

I think I mentioned this before, but it should now be even more clear that the values list in the insert statement is a comma-separated list. That’s also something I want to factor out… next(?) time. It’s not quite automatic.

Final comment: some of the XP diehards are probably screaming “yagni” (“you ain’t gonna need it”). I can see how it might be claimed–I probably don’t need a test for either the degenerate case or the embedded comma case. (I also don’t really need to extract the string utility code to a separate class, for that matter.) So, is my supposed abuse of YAGNI a good thing or a bad thing?

Comments:

I say, single responsibility principle and strong tests to communicate the behaviour of degenerate cases trumps yagni every day of the week, including dollarsday.

Database TDD Part 6: Duplication in SQL

This morning: a five-minute refactoring to further eliminate duplication in the code. SQL statements are inherently redundant. They’re also risky to put together without a test against a live database.

Let’s hit the code. Here’s the entire User class:

import java.util.*;

public class User {
   private String name;
   private String password;
   private static final String TABLE_NAME = "userdata";
   private static String[] columns = { "name", "password" };

   public User(String name, String password) {
      this.name = name;
      this.password = password;
   }

   public String getName() {
      return name;
   }

   public String getPassword() {
      return password;
   }

   public void save() {
      new JdbcAccess().execute(String.format("insert into " + TABLE_NAME + " ("
            + User.createColumnList() + ") values ('%s', '%s')", name, password));
   }

   private static String createColumnList() {
      StringBuilder builder = new StringBuilder();
      for (int i = 0; i &lt; columns.length; i++) { if (i &gt; 0)
            builder.append(',');
         builder.append(columns[i]);
      }
      return builder.toString();
   }

   public static User find(String nameKey) {
      JdbcAccess access = new JdbcAccess();
      List row = access.executeQuery(String.format(
            "select " + createColumnList() + " from " + TABLE_NAME + " where name = '%s'",
            nameKey));
      return new User(row.get(0), row.get(1));
   }
}

This was three refactoring passes, each concluded with the execution of all tests in the project. The first pass involved extracting the table name to a constant; 30 seconds. The second pass involved extracting the construction of the insert column list to a separate method that uses a String array of column names; 4 minutes. The third pass involved using createColumnList from the find method; 30 seconds.

The table name and the column lists were an obvious place to start. What about the values list in the insert statement? What about the duplication inherent in the User attributes themselves?

Another thought: the Single Responsibility Principle is getting more and more abused. It’s bad enough that the User domain class deals with persistence. Now there’s a new method createColumnList that is a generic String utility method. It belongs elsewhere. But don’t fret, we’ll get to that soon.

Original Comments:

Speaking of the single responsibility principle, how long until you make first class objects for Column and Table, and fold some of the sql creation logic into them?

–JeffBay

===
And on another note, instead of “createColumnList”, or in addition to, the more generic “join” method would be a great addition to your string utilities handbook.

Strings.join(String[] strings, String delimeter)

createColumnList(columns) {
return Strings.join(columns, “, “);
}

–JeffBay

===
Good comments, both. Not long. What I find interesting is that in 10 minutes worth of initial DB code, there’s so much refactoring that can be done.

Tonight, though, I’m almost asleep already. Maybe tomorrow night.

-j-

Database TDD Part 5: Encapsulating JDBC

You want your persistence layer to not leak its implementation details into the rest of your application. Most systems make this mistake, however: the fact that SQL via JDBC is being used is explicit in a large number of classes. Changing to a new persistence solution, such as JDO, CMP EJBs, or Hibernate, becomes a major undertaking.

Even if you’re careful to isolate all your JDBC calls to a single class, as I’m trying to do, it’s easy to ooze JDBC-specific code into the rest of your app. The culprit is java.sql.SQLException. Any code that must acknowledge this exception is now dependent on JDBC.

An easy solution would be to throw the more abstract and palatable type, Exception. But I prefer just to not propagate checked exceptions. They are a nuisance. “Inbetween” code rarely can do anything with them; top-level code can usually only report them through the user interface. Developers often lazily catch them and log an error that no one discovers until some insidious defect has escaped. For all the promises of checked exceptions, they tend to do little more than force littering of the code with try/catch blocks and throws clauses. Checked exceptions cause unnecessary duplication.

For the JdbcAccess class, then, I’ve chosen to propagate a runtime exception of a custom type. It could be of type RuntimeException, but using a custom type imparts some immediately useful information.

Driving the custom runtime exception through tests, I end up with a couple tests that look much alike. Here’s one of them:

   public void testExecuteException() {
      try {
         access.execute(BADLY_FORMED_SQL);
         fail(FAILURE_MESSAGE);
      }
      catch (JdbcException e) {
         assertException(e);
      }
   }

   private void assertException(JdbcException e) {
      assertEquals(BADLY_FORMED_SQL, e.getMessage());
      assertTrue(e.getCause() instanceof java.sql.SQLException);
   }

The other test is for executeQuery. The exception class:

public class JdbcException extends RuntimeException {
   public JdbcException(String sql, Throwable cause) {
      super(sql, cause);
   }
}

As I added the first exception test to the JdbcAccessTest class that I had from yesterday, I realized that the tearDown method deleted a sample table created solely for purposes of the test. The exception tests have no such need for a table, and thus don’t create one. This meant that the tearDown method would now throw an exception when it tried to drop a nonexistent table.

Rather than rework existing JdbcAccessTest code, the most straightforward solution is to create another test class. There’s no rule that says you can only have a single test class for each production class. Each test class can be treated as a separate fixture, with its own context that must be set up and possibly destroyed. Here’s the new test class in its entirety:

import junit.framework.*;

public class JdbcAccessExceptionsTest extends TestCase {
   private JdbcAccess access;
   private static final String BADLY_FORMED_SQL = "badly formed sql";
   private static final String FAILURE_MESSAGE = "expected exception from malformed sql";
   protected void setUp() {
      access = new JdbcAccess();
   }

   public void testExecuteException() {
      try {
         access.execute(BADLY_FORMED_SQL);
         fail(FAILURE_MESSAGE);
      }
      catch (JdbcException e) {
         assertException(e);
      }
   }

   public void testExecuteQueryException() {
      try {
         access.executeQuery(BADLY_FORMED_SQL);
         fail(FAILURE_MESSAGE);
      }
      catch (JdbcException e) {
         assertException(e);
      }
   }

   private void assertException(JdbcException e) {
      assertEquals(BADLY_FORMED_SQL, e.getMessage());
      assertTrue(e.getCause() instanceof java.sql.SQLException);
   }
}

The modifications to the production JdbcAccess code involve simply adding try/catch blocks to the public methods.

   public void execute(String sql) {
      try {
         createStatement();
         statement.execute(sql);
         closeConnection();
      }
      catch (SQLException e) {
         throw new JdbcException(sql, e);
      }
   }

   public List&lt;String&gt; executeQuery(String sql) {
      try {
         createStatement();

         ResultSet results = statement.executeQuery(sql);
         results.next();
         List&lt;String&gt; row = getRow(results);
         results.close();

         connection.close();
         return row;
      }
      catch (SQLException e) {
         throw new JdbcException(sql, e);
      }
   }

Don’t forget the best part: going back to UserTest, User, and JdbcAccessTest and eliminating all the throws SQLException clauses plus the importstatement. Your IDE should be able to help you here.

You’ll still need an application-level strategy for managing exceptions at the UI level. Best that you discuss this strategy with your team, and come up with the rules that everyone must play by. One of those rules is that tests should always demonstrate that exceptions can arise.

Database TDD Part 4: Backing Into Tests

When extracting methods into another class, the tests that cover these methods may be best suited for staying on the original class. This leaves you with no tests directly covering the extracted code.

In the case of the JdbcAccess code, the execute and executeQuery methods remain covered indirectly through tests in UserTest. My preference is to take the time and immediately add tests that directly exercise the newly exposed public interface. Here’s JdbcAccessTest:

import java.sql.*;
import java.util.*;

import junit.framework.*;

public class JdbcAccessTest extends TestCase {
   private static final String TABLE = "JdbcAccessTest";
   private JdbcAccess access;

   protected void setUp() {
      access = new JdbcAccess();
   }

   protected void tearDown() throws SQLException {
      access.execute("drop table " + TABLE);
   }

   public void testExecute() throws SQLException {
      access.execute(createTableSQL());
      assertEquals(0, count());
   }

   public void testExecuteQuery() throws SQLException {
      access.execute(createTableSQL());
      List<String> row = access.executeQuery(createCountSQL());
      assertEquals(1, row.size());
      assertEquals(0, getInt(row, 0));
   }

   private int count() throws SQLException {
      List<String> row = access.executeQuery(createCountSQL());
      return getInt(row, 0);
   }

   private String createCountSQL() {
      return "select count(*) from " + TABLE;
   }

   private int getInt(List<String> row, int column) {
      return Integer.parseInt(row.get(column));
   }

   private String createTableSQL() {
      return "create table " + TABLE + " (x varchar(1))";
   }
}

In order to get this working, I had to make a small change to the JdbcAccess code:

   private List<String> getRow(ResultSet results) throws SQLException {
      List<String> row = new ArrayList();
      for (int i = 1; i <= results.getMetaData().getColumnCount(); i++)
         row.add(results.getString(i));
      return row;
   }

So getRow is now generalized to any number of columns, which it still presumes are all Strings. This adds some expense in parsing the column when it’s not a String, as in the JdbcAccessTest method getInt (used for column count). It’s an expense that I’m willing to accept for the time being, but it does suggest that I want to make a note about running performance tests at some time in the future.

The other relevant lesson today is that I followed the refactoring step of eliminating duplication in the test, up to a point. That point is to the readability of the test in question, or more specifically, to the point that the test still clearly shows the thing that’s being tested. Eliminating duplication one more step would mean removing the execute call from testExecute, and the executeQuery call from testExecuteQuery. I’d rather not obscure that code. This is an example of where simple design rule #3 (expressiveness of code) can trump #2, elimination of duplication. Usually for me, this occurs only in test code.

Database TDD Part 3

Time to refactor. I took about 10 minutes and ran through about three different refactoring passes, running JUnit each time to ensure I maintained a green bar.

First pass: move responsibilities into a separate class, JdbcAccess. It’s actually a move that will stave off duplication at some future point, once I have another domain class with persistence needs. But right now I’m following the single responsibility principle as my primary refactoring driver.

Second pass: eliminate common code duplication. Example: extraction of the createStatement method.

I made a couple other minor refactorings, including inlining the loadDriver method into createConnection (it wasn’t pulling its own weight, violating simple design rule #4). The bigger move was divorcing the ResultSet from the need to populate the user object.

The code still has lots of problems, of course. There’s still the presumption that there’s a single row of data, and worse now, the JdbcAccess class presumes there are always two string columns.

Also: The User class still has to import java.sql (because of SQLException). And there’s more duplication in the SQL strings that we’ve not touched on yet. And worse, we now have a class (JdbcAccess) with no unit test coverage.

Code from User.java:

public void save() throws SQLException {
   new JdbcAccess().execute(
      String.format("insert into userdata (name, password) values ('%s', '%s')", name, password));
}

public static User find(String nameKey) throws SQLException {
   JdbcAccess access = new JdbcAccess();
   List row =
      access.executeQuery(String.format("select name, password from userdata where name = '%s'", nameKey));
   return new User(row.get(0), row.get(1));
}

JdbcAccess.java:

import java.util.*;
import java.sql.*;

import com.mysql.jdbc.Driver;

public class JdbcAccess {
   private Connection connection;
   private Statement statement;

   public void execute(String sql) throws SQLException {
      createStatement();
      statement.execute(sql);
      closeConnection();
   }

   public List<String> executeQuery(String sql) throws SQLException {
      createStatement();

      ResultSet results = statement.executeQuery(sql);
      results.next();
      List<String> row = getRow(results);
      results.close();

      connection.close();
      return row;
   }

   private List<String> getRow(ResultSet results) throws SQLException {
      List<String> row = new ArrayList<String>();
      row.add(results.getString(1));
      row.add(results.getString(2));
      return row;
   }

   private void createStatement() throws SQLException {
      createConnection();
      statement = connection.createStatement();
   }

   private void createConnection() throws SQLException {
      new Driver();
      connection = DriverManager.getConnection("jdbc:mysql://localhost/test?user=root&amp;password=xxx");
   }

   private void closeConnection() throws SQLException {
      connection.close();
   }
}

Database TDD Part 2

Interrupted by a busy two-week engagement, I return to my code to find… a failing test. While it’s not acceptable to check failing tests into a team environment, it’s often a useful technique to jog your memory. Check in the stuff that passes all the tests, add a new test that fails. When you come back, you know where to pick up.

Following the ten-minute rule, I coded the following in the User class, in order to quickly get a green bar:

   public void save() throws SQLException {
      loadDriver();
      Connection connection = createConnection();
      Statement statement = connection.createStatement();
      String sql = String.format("insert into userdata (name, password) values ('%s', '%s')", name, password);
      statement.execute(sql);
      connection.close();
   }

   private static Connection createConnection() throws SQLException {
      return DriverManager.getConnection("jdbc:mysql://localhost/test?user=root&amp;password=xyz");
   }

   private void loadDriver() throws SQLException {
      new Driver();
   }

   public static User find(String nameKey) throws SQLException {
      Connection connection = createConnection();
      Statement statement = connection.createStatement();
      String sql = String.format("select name, password from userdata where name = '%s'", nameKey);
      ResultSet results = statement.executeQuery(sql);
      results.next();
      String name = results.getString("name");
      String password = results.getString("password");
      results.close();
      connection.close();
      return new User(name, password);
   }

All sorts of bad practices, but this was the only way I was going to get the test passing within 10 minutes. I used MySQL since it’s a known quantity to me.

The test makes the presumption that there is already a database available with the userdata table defined. This is the start of where things could go very wrong. It’s one of the bigger issues I’ll discuss soon.

There are many things that may be of concern:

  • hardcoded literals strewn throughout
  • multiple connections being created
  • database code in domain class
  • hardcoded SQL strings
  • not protecting close statements with finally block
  • rerunning the test results in multiple entries in the userdata table (I have no constraints turned on)
  • loading the driver looks nonstandard (no Class.forName?)
  • save must be called before find, otherwise the driver’s not loaded
  • questionable error handling strategy
  • careless presumption that there is at least one result, and that it’s the result we’re looking for

All that in just 10 minutes worth of code. And of course duplication. Spot the duplicate code!

Next time out we’ll start tackling some of these issues. Starting, as always, with duplication.

Practicing What I Preach

As I mentioned in an earlier post, I believe in continually honing my programming skills through practice. My problem of choice is a database layer, code that allows for easy persistence of domain objects. I’ve built one of these several times, and each time it comes out quite a bit differently. It’s a good exercise in maturing a design through TDD.

Over the next umpteen blogs I’ll deliver on rebuilding this layer one more time. I don’t have a real application in mind, yet, so I’ll start with some simple presumptions. In lieu of an application, I’ll build specific persistence for a “user” object (a name and a password). Then I’ll add some complexity and repetitious application needs in order to flesh out a generalized solution.

First things first, I need a User class. Building that is simple enough. I usually drive crummy data classes through a quick creation test:

import junit.framework.*;

public class UserTest extends TestCase {
   private static final String name = "a";
   private static final String password = "b";
   private User user;
   
   protected void setUp() {
      user = new User(name, password);
   }
   
   public void testCreate() {
      assertEquals(name, user.getName());
      assertEquals(password, user.getPassword());
   }
}

The implementation for that is easy enough. In trying to keep this and future blog posts short, I’ll always make the entire codebase available (once I get the project set up in CVS). The post will capture only key points and just enough code to make those points.

The next step is to design in the interface that drives persistence. Here’s a simple start:

   public void testPersist() {
      user.save();
      User retrievedUser = User.find(name);
      assertEquals(name, retrievedUser.getName());
      assertEquals(password, retrievedUser.getPassword());
   }

This brings up one of those age-old questions: do the save and find methods belong in the User class or elsewhere? Please drop your comments; I’m not going to worry about the answer until I have a second class that requires persistence services.

I can get this test to work by storing a saved user in a static-side User collection. Or, obnoxiously, since I only have one User, I can use the Monostate pattern (all state variables are static), and derive the retrieved user from the static fields. Either one is perfectly acceptable, and in the spirit of the “you ain’t gonna need it” mantra that XP diehards babble.

But I’m not going to do that. Or rather, I ain’t gonna do that. I have a specific business need, namely, real persistence. The story is, “data is not lost when the server restarts.” Good enough for me. I need persistence, and I can imagine there’s a story about reliability that suggests an RDBMS is the best place to start.

This is where I break from the XP purists: inevitable architectural decisions don’t always need to be test-driven. Yes, there’s some value to finding out where the tests and associated zealot refactoring will lead you. And sometimes, you’ll end up in a different spot, not even needing an RDBMS. But on rare occasions, you just know.

Enough for today, I’ve got to get the CVS project set up. Next I’ll work on getting my persistence test to pass.

Atom