Tuesday, December 2, 2008

Castle Active Record and ActiveRecordMediator

the ActiveRecord pattern as it's typically implemented works like so.
step 1 - Take a base class with all the persistence methods you need (save, update, delete, find, etc)


public class ActiveRecordBase{

public static object Find(ICriteria criteria)
{
//return object

}
public void Save()
{
//save object to db here
}
// rest hidden
}


step 2 - take all your db classes and have them inherit from them (optionally passing on column configuration or whatever you'll make available).


public class Account : ActiveRecordBase
{

public int Id{get;set;}
public string Name{get;set;}
public AcctType Type {get;set;}

}


client code ends up looking like so



public void static Main(string[] args)
{
Account acct = new Account();
acct.Name = "Max Earnings";
acct.AcctType = AcctType.Checking;
acct.Save()

Account[] acctsfromdb = Account.FindAll();


}


Now alot of people like this pattern. They'll attach behavior or methods to the properties or even add their services to the same class (in the above example maybe adding a method that calculates current interest rate against a lookup table).

I however have always had issues with unit testing this way. You end up testing against a database directly (which is substantially slower), and your class becomes decorated with all these persistence methods. Plus I've never personally enjoyed mixing data classes and method classes (called services in Domain Driven Design), this confuses the picture for me and muddies things up (what do you do when you have an interaction between multiple classes for example?)

So how do you get the simplicity of db access with ActiveRecord but not have to worry about having database tied classes? Well you can use an orm that's persistent ignorant like Nhibernate (or several others), which involves alot of by hand configuration (I say this watching the fluent nhibernate project closely), session, transaction handling, and generally gives you more control than you need for alot of starting projects. If only you could find a nice middle ground.


Enter Castle ActiveRecord and ActiveRecordMediator class. ActiveRecordMediator is NHibernate for dummies (or lazys). So back to our earlier example


[ActiveRecord]
public class Account
{
[Property]
public int Id{get;set;}

[Property]
public string Name{get;set;}

[Property]
public AcctType Type {get;set;}


}


now client code looks like so


public static void Main(string[] args){


Account acct = new Account();
acct.Name = "Max Earnings";
acct.AcctType = AcctType.Checking;
ActiveRecordMediator<account>.Save(acct)

Account[] acctsfromdb = ActiveRecordMediator<account>.FindAll();


}



So recap the difference in code is small. Lines of code is about the same..but now I can decouple the use of my objects away from database...but HOW you ask?

Ahh yes let me carry you that last step.


public interface IRepository<T>{

public void Save(T t);
public T[] FindAll();
public void Update(T t);
//etc etc


}
public class DbRepsository<T>:IRepository<T>where T : class
{

public void Save(T t){

ActiveRecordMediator<T>.Save(t);
}

public T[] FindAll()
{
var records = ActiveRecordMediator<T>.FindAll();
return records;
}
//etc, etc

}
public class Client{
private IRepository<Account> _db;


public Client(IRepository<Account>() db)
{
_db = db

}


public void AddAccount(Account acct)
{
_db.Save(acct);
}

}

public class FakeDb<T>:IRepository<T> where T : class
{
private List<T> records = new List<T>();

public void Save(T t){
records.Add(t);
}
public T[] FindAll(){
return records.ToArray();
}
//etc etc

}


[TestFixture]
public class TestClientCode{

[Test]
public void should_save_record_to_repository(){

var memdb = new FakeDb<Account>();
var client = new Client(memdb);
Account acct = new Account();
acct.Name = "Max Earnings";
acct.AcctType = AcctType.Checking;

client.Save(acct);

var fromdb = memdb.FindAll();
Assert.AreEqual(1,fromdb.Length);

}




Final Recap if you look at the test we can now easily write unit tests (with no db to worry about). With the simple example provided here there isn't much obvious gain, but start adding your persistence classes to controllers, services and any other usage you can think of and this pays back the initial work we did here 10 fold.

No comments: