In my previous blog entry, Improving on POCO Properties, we looked at the benefits we can reap for our domain objects if we represent properties with dedicated helper objects instead of restricting ourselves to the usual C# syntax.

As you may have guessed by now, there are improvements that can be made in other areas as well. In this post, the focus will be on composite objects.

As a concept, an Order is a single item - a request for a set of desired items to be supplied. As a matter of modelling, the set of items is usually broken out into a list of related OrderLine instances. Approaching the model in this manner is very pragmatic, as it allows us to leverage the power of our tooling, but it is important to remember that an OrderLine is mostly a modelling artifact.

In C#, our Order and OrderLine classes might include details something like this:

class Order
{
    IList<OrderLine> Lines { get; }
}

class OrderLine { } 

Using a simple List to contain the lines provides a minimal set of semantics - lines may be added, removed, and iterated - but nothing more.

Significantly, exposing a list in this manner opens our domain Order up for external manipulation. Instead of the Order having sovereign control over its own information, as you would normally expect, any snippet of code with the will to do so may change the list of order-lines, even if that would make no sense given the current state of the object.

One way to address this limitation is to change the declarations to this:

class Order
{
    IEnumerable<OrderLine> Lines { get; }
    void AddLine(OrderLine line);
    void RemoveLine(OrderLine line);
}

This puts our object in the execution path for modifications to the list of order-lines, but has drawbacks of it’s own.

Importantly, any methods to modify the list of order-lines are no longer found on Lines, but instead on the parent object , mixed in with other Order methods, thus impairing discoverability. The author of the Order class has to resolve an implementation tension - either spend a tedious time writing simple code to simulate a full IList implementation (although without actually achieving IList compatibility), or write a minimal interface that perhaps won’t meet the needs of consuming code.

Fortunately, there is a better way - by using a specialist helper object to provide the additional semantics we require. The declaration of Order simplifies:

class Order
{
    DomainCollection<OrderLine> Lines { get; }
}

Of course, this only becomes interesting when the declaration for DomainCollection is reviewed:

class DomainCollection<T>: IList<T>
{
    IEnumerable<T> OriginalItems { get; }
    bool Modified { get; }
    bool IsEmpty { get; }
    void Clear();
    void Commit();
    void Revert();
    event EventHandler<ItemAddingEventArgs> ItemAdding { add; remove; }
    event EventHandler<ItemRemovingEventArgs> ItemRemoving { add; remove; }
}

As you will recall from our previous discussion of the Property object, the Modified and OriginalItems properties provide for true dirty tracking, though (of course) in this situation the logic required for the test is more complex. Also included here is full support for transactionality, permitting changes to be committed or reverted as necessary.

Implementing the IList<T> interface provides for maximum compatibility - even to passing DomainCollection<T> itself into methods expecting a simple IList<T> or even IEnumerable<T>. Another possibility introduced by this implementation is the ability to leverage Linq to Objects functionality in useful ways:

// Find new Order Lines
var newOrderLines = Lines.Except(Lines.OriginalItems);

// Find removed Order Lines
var removedOrderLines = Lines.OriginalItems.Except(Lines);

Addressing the problem discussed earlier, where external code might manipulate the list in violation of the current object state, the events ItemAdding and ItemRemoving allow the parent object to exert veto rights over any attempted modification. These events would be hooked when the helper object is created, usually within the constructor of the parent object.

I hope that you will now be thinking of other ways that the DomainCollection<T> class might be used to make life easier. What other ways might simple helper classes make our Domain Model more expressive?

Comments

blog comments powered by Disqus