In DDDD 4 I was lucky enough to get some commentary from an astute reader Jimmy Bogard, he wrote in his comment:
I think I'd have to respectfully disagree here. Valid state can be enforced in more ways than an immutable object. Immutability leads to everything supplied in the constructor. If I have coarse-grained services, messages can get fairly large. Suppose instead of Email, you had something like Order. Order contains LineItems (flattened out), maybe Addresses, ShippingOptions, etc etc. This is quite a lot to satisfy in an immutable fashion. For simple message, I like to make them immutable to satisfy invariants. But if I'm using things like XML Serialization, it's much simpler to have a plain DTO and a Validate method. I leave it to my Factory to satisfy invariants. As for other Value Object semantics like equality, I never got much use out of those. I never compared DTOs, as they were strictly used at the service layer boundary. Nothing interesting was ever done with them, as they are immediately used with a Mapper to get back to real domain objects. When I tried this concept (DTOs are Value Objects) a couple of years back, it started out nicely but broke down in our real-world scenarios of coarse-grained services. I chalked it up to my misplaced desire to model everything in our system in DDD concepts (entity, value object, service). Perhaps you've had a different experience.
The builder helps alleviate a lot of this pain while at the same time making our code more readable. I have talked about fluent builders in the past in A Use for Extension Methods but I will briefly go through how they work again. The basic parts of the pattern are to create a class that is immutable and has internally the same members that will be required to call the constructor of the target object. Properties and methods are exposed with fluent names to help build the target object. The target object's constructor is eventually called through an implicit cast operation. As is always the case code speaks 1000 words.
Let's take a simple message.
InsertOrderCommand ioc = new InsertOrderCommand(null, MarketSide.Buy, "B12345678", 0.12m, "NT",
TestDate, 1000, false, 100, 1000, false, TestDate, OrderDuration.Day, ExchangeGateways.CNQToronto, false, false);
YUCK!
Let's try rewriting that with a builder.
InsertOrderCommand ioc = New.InsertDayOrder
.On(ExchangeGateways.CNQToronto)
.FromBroker(10)
.ToBuy(1000)
.OfSecurity("NT")
.At(0.12m)
.WithUniqueOrderNumberOf("B12345678");
note that the builder does not always match up 1-1 with the object ... in this example ToBuy(Volume) sets that the order type is of type buy, it sets another field, and it sets the volume.
Phew that's a whole lot easier to read! Let's look at how to have this happen. First let's look at a message.
[Serializable]
public class EquityQuoteMessage : ILevelOneCommand, IHasSecurityInformation
{
public readonly DateTime Time;
public readonly string Symbol;
public readonly decimal BidPrice;
public readonly decimal AskPrice;
public readonly ExchangeGateways Gateway;
public EquityQuoteMessage(ExchangeGateways _Gateway, string _Symbol, DateTime _Time, decimal _BidPrice, decimal _AskPrice)
{
Gateway = _Gateway;
Symbol = _Symbol;
Time = _Time;
BidPrice = _BidPrice;
AskPrice = _AskPrice;
}
public string GetSummary()
{
return "InsertEquityQuote: " + Gateway.ToString() + " " + Symbol + " " + BidPrice + "-" + AskPrice;
}
ExchangeId IHasSecurityInformation.ExchangeId
{
get { return ExchangeGatewayIdTranslator.TranslateGatewayToExchangeId(this.Gateway); }
}
string IHasSecurityInformation.Symbol
{
get { return this.Symbol; }
}
}
Now ... we need a builder for this message.
public class EquityQuoteMessageBuilder {
internal readonly ExchangeGateways m_Gateway;
internal readonly string m_Security;
internal readonly DateTime m_Time;
internal readonly decimal m_BidPrice;
internal readonly decimal m_AskPrice;
[DebuggerStepThroughAttribute]
public EquityQuoteMessageBuilder OnGateway(ExchangeGateways _Gateway) {
return new InsertEquityQuoteCommandBuilder(_Gateway,m_Security,m_Time,m_BidPrice,m_AskPrice);
}
[DebuggerStepThroughAttribute]
public EquityQuoteMessageBuilder ForSecurity(string _Symbol) {
return new InsertEquityQuoteCommandBuilder(m_Gateway,_Symbol,m_Time,m_BidPrice,m_AskPrice);
}
[DebuggerStepThroughAttribute]
public EquityQuoteMessageBuilder At(DateTime _Time) {
return new InsertEquityQuoteCommandBuilder(m_Gateway,m_Security,_Time,m_BidPrice,m_AskPrice);
}
[DebuggerStepThroughAttribute]
public EquityQuoteMessageBuilder WithSpreadOf(decimal _BidPrice, decimal _AskPrice) {
return new InsertEquityQuoteCommandBuilder(m_Gateway,m_Security,m_Time,_BidPrice,_AskPrice);
}
private EquityQuoteMessageBuilder(ExchangeGateways _Gateway,string _Symbol,DateTime _Time,decimal _BidPrice,decimal _AskPrice) {
m_Gateway = _Gateway;
m_Security = _Symbol;
m_Time = _Time;
m_BidPrice = _BidPrice;
m_AskPrice = _AskPrice;
}
public EquityQuoteMessageBuilder {}
public static implicit operator EquityQuoteMessage(EquityQuoteMessageBuilder _Builder) {
return new InsertEquityQuoteCommand(_Builder.m_Gateway, _Builder.m_Security, _Builder.m_Time, _Builder.m_BidPrice, _Builder.m_AskPrice);
}
}
I have written a generator to make skeletons of these builder classes that I may post which simplifies this process greatly (especially if you use resharper ctrl+rr or f2 everything else is done) but I will only release it if people promise not to make fun of the code as it was written in an afternoon just to save time when writing a few and looks like it was written by a second year student :-)
Aaron over at elutian uses a structurally identical pattern (except for the additional use of extension methods in A Use for Extension Methods) in his post Fluent Fixtures the key difference between what I call a Fluent Builder and what he calls a Fluent Fixture is in the intent. Aaron uses his fixtures only for the initialization of setup data for tests which they are EXTREMELY valuable for (especially when dealing with messages as it produces a soluble method of expressing what is in the method) ... My fluent builders are used not only for the initialization of test data but also for the creation of real objects (I might even go so far as to say that the construtors for messages should be made internal and you should only expose builders though in some extremely high performance conditions you might want to actually use constructors).
Jimmy Bogard hit the nail on the head in that complex messages can be a nightmare to build immutably because they get really big. What we do to get around that is we use an mutable builder that we then cast to an immutable message. This gives us the best of both worlds although the builders can be annoying to type, since essentially we have made explicit a transient mutable version of the message and a fixed immutable version of the message.
To be more clear, let's try a bigger example like the one that Jimmy mentions and see how bad it is to create a bigger message. I will use a DTO example since they tend to be the offenders
OrderDTO order = New.OrderDTO
.WithId(SomeGuid)
.ShippingTo(New.Address .....)
.AddLineItem(New.LineItem.For(12).Of("Product 1").At(15.99)
.AddLineItem(New.LineItem.For(1).Of("Product 2").At(22.97)
.AddLineItem(New.LineItem.For(15).Or("Product 3").At(15.22)
naturally you could use a loop etc when building the message (and sometimes in terms of test fixtures you may find your self actually referencing the Builder then later adding more items to it similar to how an object mother would work but this is another post)
Of course we can go a different route than this. You will notice that a natural break is occurring in the setup of our message between the root and the pieces under the root. What if instead of trying to create one really big message we created a few smaller ones just like how our actual setup is working. In other words what if instead of having an OrderDTO as one big message we introduced a container to hold smaller messages and relate them as being one something like:
SnapShot
CreateOrderMessage
AddLineItemMessage
AddLineItemMessage
AddLineItemMessage
EndSnapShot
This would help prevent us from having to deal with the larger items but each method has their own benefits. The main benefit of the first method is that its schema exists within the message. Especially when dealing with external clients or other types of integration points this can be extremely beneficial (there is no need of logic to figure out how to build the data). When combined with something like XML this can be extremely powerful.
The second method can also be extremely powerful in a closed environment for a few reasons. In many circumstances it is actually simpler to send over these smaller pieces, the big problem with this methodology is that you need to have domain logic existing on the client in order to know how to process these smaller messages to get the client to a state matching what the originator of the message intended. As we will see later on though, in some circumstances (especially when we want disconnected access) some of this logic will be distributed for other reasons anyways so it may become a viable option.
Posted
Tue, Apr 15 2008 11:11 PM
by
Greg