I always waver on the decision between creating duplication and then eliminating it, or staving it off immediately before you would have created it. I think the former is safer in terms of coming up with a better solution. It is, however, slower, something we'll see today.
I wanted to force the issue of duplication by adding a second domain class, so as to figure out how the persistence driver was going to shake out. I created a new class Customer along with its associated test class. Copy and paste, literally. It's a stupid data class, but let's pretend it has legitimate behavioral capabilities that forced it into existence. The customer class contains an id and a name, both strings.
Same thing for Customer persistence needs: new CustomerAccessTest and CustomerAccess classes are pretty much copy-and-pasted. Is copying and pasting code bad? In my view, nothing's bad until it's checked in. Here it's a tool to make the duplication explicit, visible in all its gory details, right in front of my face.
The alternative would be to factor relevant code from the UserAccess class. But I find it slightly more difficult to try to put all of that into my head. Here I can start with the obvious duplication and slowly refactor to an appropriate solution.
The first refactoring is to extract code that generates SQL into a completely separate class.
public class UserAccess {
private static final String TABLE_NAME = "userdata";
private static String[] columns = { "name", "password" };
public void save(User user) {
String sql = new SqlGenerator().createInsert(TABLE_NAME, columns, createValuesList(user));
new JdbcAccess().execute(sql);
}
private String createValuesList(User user) {
Transformer ticWrapper = new Transformer() {
public String transform(String input) {
return StringUtil.wrap(input, '\'');
}
};
return StringUtil.commaDelimit(new String[] { user.getName(),
user.getPassword() }, ticWrapper);
}
...
package persistence;
import util.*;
public class SqlGenerator {
public String createInsert(String tableName, String[] columns, String values) {
return String.format("insert into %s (%s) values (%s)", tableName,
createColumnList(columns), values);
}
private String createColumnList(String[] columns) {
return StringUtil.commaDelimit(columns);
}
}
The createValuesList method should obviously belong to in the SqlGenerator class. The problem is how to get the user fields to the SqlGenerator class such that it doesn't create a backward dependency. Easy enough: prepopulate the array of field values and pass it over.
// UserAccess code
public void save(User user) {
String[] fields = { user.getName(), user.getPassword() };
String sql = new SqlGenerator().createInsert(TABLE_NAME, columns, fields);
new JdbcAccess().execute(sql);
}
package persistence;
import util.*;
public class SqlGenerator {
public String createInsert(String tableName, String[] columns, String[] fields) {
return String.format("insert into %s (%s) values (%s)", tableName,
createColumnList(columns), createValuesList(fields));
}
private String createColumnList(String[] columns) {
return StringUtil.commaDelimit(columns);
}
private String createValuesList(String[] fields) {
Transformer ticWrapper = new Transformer() {
public String transform(String input) {
return StringUtil.wrap(input, '\'');
}
};
return StringUtil.commaDelimit(fields, ticWrapper);
}
}
The rest of the code refactoring is pretty straightforward, based on this direction. When I finished, my CustomerAccess class looked like this:
package domain;
import java.util.*;
import persistence.*;
public class CustomerAccess {
private static final String TABLE = "cust";
private static String[] COLUMNS = { "id", "name" };
public void save(Customer customer) {
String[] fields = { customer.getId(), customer.getName() };
String sql = new SqlGenerator().createInsert(TABLE, COLUMNS, fields);
new JdbcAccess().execute(sql);
}
public Customer find(String idKey) {
String sql = new SqlGenerator().createFindByKey(TABLE, COLUMNS, COLUMNS[0], idKey);
JdbcAccess access = new JdbcAccess();
List<String> row = access.executeQuery(sql);
return new Customer(row.get(0), row.get(1));
}
}
This looks a like nicer, but it also still looks way too much like UserAccess. The fact that data is stored on the domain objects in explicit fields is going to continue to be a nuisance--unless I do something about that.
I'm essentially building metadata here, about the database tables and the domain objects for which they hold data. Where are these tables coming from? Right now, the presumption is that they already exist. What if I were to define the tables myself from code? Or what about the opposite--obtain all the appropriate data from the database itself? There are myriad ways to proceed from here; I'm going to need to decide on a direction soon. I'm willing to go in any direction--your input is welcomed.
For now, I'm going to finish cleanup on the code base, making some simple name changes and adding tests where needed. Here's the code.
February 2004 March 2004 May 2004 September 2004 October 2004 January 2005 February 2005 September 2005 October 2005 November 2005 December 2005 January 2006 February 2006 March 2006 June 2006 August 2006 January 2007 February 2007 March 2007 April 2007 September 2007 October 2007 November 2007 December 2007 January 2008