Monday, April 14, 2008

Simple Dirty Tracking in Domain Objects

This article shows how to add dirty-tracking to domain objects (aka business entity - BE) by changing their code and adding a public bool IsDirty property. This design is simple and intrusive, and is best suited when you have to implement your BE objects yourself anyway. It requires no usage of BE interfaces, deriving and overriding, AOP, nor any DI-container.

If you cannot change the BE code, then consider implementing non-intrusive logic to compare a unit-of-work BE with its original values; either by reading the original BE from the repository and perform a recursive Equals(originalEntity) check of the BE's object graph, or by using an extended identity map that has original copies of the unit-of-work BE objects and performing the same Equals(originalEntity) check. The latter approach is better for client-side dirty-tracking; while the former is better suited for server-side implementations (get - compare - save).

Never implement dirty-tracking in client applications by using click events and the like, to set the dirty flag from view or presenter code. Implementing dirty-tracking in client code breaks both the SRP and DRY principles, first by making dirty-tracking a responsibility of the view/presenter instead of the BE object, and then by repeating dirty-tracking code across all places where the BE object is used.

If your BE objects implements INotifyPropertyChanged to enable data-binding, then the event is a good place to add dirty-tracking. Note that this dirty-tracking mechanism depends on all changes to the BE state being done through the property setters. Luckily, the _isDirty flag can also be set directly from anywhere within the BE code if necessary.

protected void RaisePropertyChanged(string propertyName)
{
//NOTE: must be here to run even if no handlers are registered
this._isDirty = true;

PropertyChangedEventHandler propertyChanged = this.PropertyChanged;

if (propertyChanged != null)
{
propertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}

If your BE is a DDD "aggregate root" and thus contains a complex object graph, then your BE should implement an IsDirty method, in addition to the private _isDirty member, that iterates through all child objects and checks their IsDirty status. Each object within the aggregate root must implement its own dirty tracking mechanism; either as just an IsDirty property for simple objects, or both an _isDirty flag and an IsDirty method for iterating its children also for complex objects.

public bool IsDirty()
{
if (this._isDirty) return true;

bool isDirty = this._isDirty;
foreach (Allocation allocation in _allocationList)

{
if (allocation.IsDirty) isDirty = true;
break;

}
return isDirty;

}

You could add event handlers to listen for the INotifyPropertyChanged event from contained child objects, but this is not recommended as you would need to add and remove a dynamic number of event handlers based on how many objects there are inside the aggregate root throughout its complex object graph - also when objects are added or removed from child collections.

No comments: