Ok, so now that we have talked about the Business Primitive concept lets go through some of the actual examples. Let's take an example of a 'Loan' class.
public class Loan
{
public int Id { get; set; }
public int InterestRate { get; set; }
public DateTime TradedOn { get; set; }
public DateTime SettledOn { get; set; }
public decimal Principal { get; set; }
}
After some small tweaks.
public class Loan
{
public int Id { get; set; }
public Rate Interest { get; set; }
public DateTime TradedOn { get; set; }
public DateTime SettledOn { get; set; }
public Money Principal { get; set; }
}
Already the code feels more robust to me. The dates aren't that interesting to the business other than they are dates so they haven't yet been uplifted into 'business primitives' but Interest has been converted to 'Rate', which immediately helps with math and providing meaningful math like methods, as well as Principal which is now 'Money' which also helps with math and the more pernicious rounding issues.
So how was 'Rate' implemented? What does it look like?
[DebuggerDisplay("{_rate}%")]
public class Rate
{
int _rate;
private Rate() { }
//factory methods
public static Rate FromWholePercent(int rate) { new Rate(){_rate = rate};}
//equality overrides here
//operator overrides here
}
First, we have the 'DebuggerDisplay' attribute which is my new best friend, it allows you to customize how the debugger displays your object in the debug view, very helpful. I have wrapped the previous rate value in a very simple fashion, and now we have a nicely centralized place to put math based functions (myRate = baseRate+riskRate). I have also added a factory method that help explain how this Rate object is being made. From whole percents such as 78% or 22%. Because we have hidden the way we actually store the value, we could also add support for 22.2% by adding a new factory method and changing the internal storage with out changing any other part of the code base. Very cool.
Ok, so what about saving these value objects and now I need to bind them to the UI how do I get at that hidden variable? I have taken quite a few paths when trying to implement this and the way that I do things now is a combination interface + base class. If you are anything like me you are thinking 'base class, yuck' and well I still do but this is the best way that I have come up with and I am open for suggestions. :)
So my 'business primitives' inherit from an abstract class called 'Primitive<T>' this class helps to streamline the creation of my business primitives. It also EXPLICITLY implements an interface called ValueObject<T> that exposes the underlying value, that can be used by UI controls and persistance frameworks with out mucking up my public API. I love that I don't see 'SetValue' and 'GetValue' unless I want to (yes, this could have been a property, not sure why I choose this). Here is the interface:
public interface ValueObject<T>
{
void SetValue(T input);
T GetValue();
}
and the base class
[DebuggerDisploy("{_primitiveValue}")]
public class Primitive<T> :
ValueObject<T>
{
T _primitiveValue;
public Primitive()
{
}
public Primitive(V primitiveValue)
{
_primitiveValue = primitiveValue;
}
void ValueObject<T>.SetValue(T input)
{
_primitiveValue = input;
}
T ValueObject.GetValue()
{
return _primitiveValue;
}
//equality stuff
}
I really wanted to do some tricky generics to try and implement the operator overloads in the base class as well, but the compiler wasn't to happy about that so those still have to be implemented in the 'Rate' class. Now my 'Rate' object looks like this:
public class Rate :
Primitive<int>
{
private Rate() { }
private Rate(int value) : base(value){}
//factory methods
public static Rate FromWholePercent(int rate) { new Rate(rate);}
//operator overloads should be implemented here
public bool Equals(Rate other)
{
if (ReferenceEquals(null, other)) return false;
if (ReferenceEquals(this, other)) return true;
return base.Equals(other);
}
}
For saving these buggers? Well, using NHibernate we can persist this as an 'int' using an IUserType, which makes things very simple and still manages to keep NH out of our domain.
Some links on implementing IUserTypes
Otherwise, with the interface you now have the means to get access to the value, so customize for your needs!
Now that we have addressed the saving aspect, what's a good way to present these 'business primitives'? Because
inputLoanPrincipal.Text = loan.Principal.ToUiString();
sucks. I would suggest some extension methods on your UI controls that take Primitive<T> as the type and can then cast to the ValueObject and get access to the value. Or extending the UI binding much in the way FubuMVC has, seems like a really solid way to
approach things too.
I hope this helps :)
-d
Posted
Thu, Feb 4 2010 5:18 AM
by
drusellers