Tuesday, September 21, 2010

Implementing a ‘Money’ type in an ASP.NET MVC and NHibernate application.

Years ago I read Kent Beck’s seminal Test Driven Development. The first third of the book is a complete worked example of building a Currency type using TDD and I remember thinking at the time that this was an incredibly powerful technique. Rather than using basic data types -  int, string, DateTime - we could define what we actually meant. Rather than having a property Age of type int, we would define an Age type. Rather than two string properties we could define a Name type. One of the factors in moving Suteki Shop from Linq-to-SQL to NHibernate was NHibernate’s support for more complex finer-grained mapping.
When I first hacked together Suteki Shop, I used the decimal type everywhere I needed to represent money. This has mostly worked well for the simple scenario where a shop only has a single currency and that currency is sterling (£). But anyone wanting to sell in US dollars had to find every one of the many instances of “£” and replace them with “$”, and if you wanted to do something fancy with multiple currencies, you would have been out of luck. What I needed was a Money type. It’s not a trivial refactoring. Here are the steps I needed to take:
Create a Money type that behaved as far as possible like a .NET numeric type.
Create an ASP.NET MVC IModelBinder that knew how to bind the Money type.
Create an NHibernate IUserType that knew how to persist the Money type.
Change every point in the code that was using decimal to represent money to use the Money type instead.

Create a Money type
I wanted to create a Money type that would behave nicely when I did arithmetic with it. So I wanted to be able to write expression like this:
var x = new Money(43.5M);
var result = x * 3 + x/2 - 4;
result.Amount.ShouldEqual(148.25M);
To achieve this you have to write a lot of operator overloads for all combinations of operator, Money <-> Money, Money <-> decimal and decimal <-> Money. I won’t bore you, if you want to see the gory details, you can view the code here.
Read more: CODE RANT