The Active Record pattern has gained a lot of attention lately, particularly because of it’s use in the wildly popular Ruby On Rails (“RoR’) framework. Other frameworks which employ the Active Record pattern include the Castle Project, which aims to simplify the creation of enterprise and web applications for .NET developers. If you are new to the Active Record design pattern, it is an object-relational mapping and object persistence pattern for binding business objects with database records. Specifically:
Active record is an approach to access data in a database. A database table or view is wrapped into a class, thus an object instance is tied to a single row in the table. After creation of an object, a new row is added to the table upon save. Any object loaded gets its information from the database; when an object is updated, the corresponding row in the table is also updated. The wrapper class implements accessor methods or properties for each column in the table or view.
Therefore, typical implementations of the Active Record pattern consist of instance properties representing a record’s fields in the database, instance methods that act on that specific record and static methods acting on all records, i.e. the database table or view. David Hayden gives a nice overview of how this pattern might be implemented in his article Active Record Design Pattern – Domain Driven Design and Domain Layer – Object Persistence.
I would hazard to guess that almost anyone who has developed web applications with Ruby On Rails would testify to the utility and even aesthetics of the Active Record pattern. For the record, I share this admiration. However, I can see problems lurking inside of the various implementations, in terms of both design principles and testability.
Problem #1: Static Methods
As mentioned above, each Active Record class maps to a corresponding database table (or view), and each instance of such a class maps to a record in that table. So, to create a new Account record you might do the following (I’ll use c# syntax):
Account newAccount = new Account();
You then might set some field values and save it:
newAccount.Name = "Joe"; newAccount.Save();
However, if you want to get an account from the database you use a static method (because you are querying the whole table, not a specific record, to find it):
Account joesAccount = Account.Find("Joe");
Some would say using Static methods simply amounts to procedural programming, and therefore is poor Object Oriented design. Others would say static methods are death to testability.
Problem #2: Global Configuration Settings
In our example above, the Account class needs to connect to a database (or some source). You will find that most Active Record implementations have some kind of application config file. For example, Ruby On Rails uses a YAML file to persist the application’s database connection settings. Therefore there is no dependency injection on the Account class in my example, and by extension, on the Account instances. As we should all know by now, looking for things is very, very bad!
Towards A Better Active Record
A better pattern might still include classes that represent the various database tables, but instead define separate logical state containers to represent the table’s records. Logical state containers are classes that simply define a set of fields with associated accessor methods/properties – you can almost think of it as a glorified struct. The classes that represent the tables would be instantiated by passing the connection settings to its constructor:
AccountTable accounts = new AccountTable(myConnectionSettings);
We can now query the instance of the database table that corresponds to our connection settings to retrieve records in the form of a simple logical state container:
AccountRecord joesAccount = accounts.Find("Joe");
Updates to the database would be done by passing instances of the proposed logical state containers into instance methods of the table classes:
joesAccount.Name = "Joe Jr."; accounts.Save(joesAccount);
Separating responsibilities for the table and the records into two separate classes provides a better design and a decent shot at genuine testability.