public class Person
{
public IList<String> SpecialBirthPlaces;
public static readonly DateTime ImportantDate;
public String BirthPlace {get;set;}
public DateTime BirthDate
{
set
{
if (BirthPlace!=null &&
value < ImportantDate &&
SpecialBirthPlaces.Contains(BirthPlace))
{
BirthPlace = DataBase.GetBirthPlaceFor(BirthPlace, value);
}
}
}
}
This is an attempt to encapsulate a simple rule in my domain model. The rule I'm trying to capture is: when, for some reason, we update a person's birth date (e.g. there was a mistake in the original user input) we need to check the person's birthplace and replace it with some other value from a database, if it is listed in our database as a special birthplace.
However, I have 2 problems implementing it:
This rule modifies domain entity state (property) and I need to reflect this change in the user interface. My domain model is POCO. I could put this logic in the ViewModel, but this is wrong because it's not UI logic. It's an important domain rule which I need to capture.
My list of SpecialBirthPlaces is pretty big and I don't want to populate it every time I get a customer from database. Also, I need to get a replacement for Birthplace when the rule is satisfied. As I said the list of special birthplaces and replacements for this is very big and is stored in the DB.
How to implement the logic I need in DDD style?
The way I've encapsulated this problem, which is modification tracking, is with the Unit of Work pattern. I have my DDD repositories associated with a unit of work, and I can query the unit of work for any set of entities which I get from any of the repositories to see which are modified.
As for the large collection, it appears to be a read-only set. One way to handle this is to preload and cache this locally if it is ever accessed, then the repositories can run queries against the in-memory version. I use NHibernate, and it is easy to handle this case with it. If it is way too big to store in RAM (like 100s of MB or more), you'll probably need to special case repository queries against it, so that the SpecialBirthPlaces.Contains(BirthPlace)
query is executed on the database (perhaps in a stored proc, ha!). You'd probably want to express SpecialBirthPlaces
as a repository of entities, rather than just a big collection of strings, which would allow the "Query" pattern to free you from needing to load the entire thing.
After this lengthy narrative, here's some example:
public class BirthPlace
{
public String Name { get; set; }
}
public class SpecialBirthPlace : BirthPlace
{
}
public class Person
{
public static readonly DateTime ImportantDate;
public BirthPlace BirthPlace { get; set; }
public DateTime BirthDate
{
get; private set;
}
public void CorrectBirthDate(IRepository<SpecialBirthPlace> specialBirthPlaces, DateTime date)
{
if (BirthPlace != null && date < ImportantDate && specialBirthPlaces.Contains(BirthPlace))
{
BirthPlace = specialBirthPlaces.GetForDate(date);
}
}
}
Having a method where you pass in the corrected birth date is a better design since it tells you via the parameters what is needed to actually correct the birth date: a repository (i.e collection) of SpecialBirthPlace entities and the correct date. This explicit contract makes it clear what the domain is doing, and makes the business needs clear just by reading the entity contracts, where putting the whole collection in the state of the entity hides it.
Now that we've made BirthPlace into an entity, we can see that there may be one more optimization to make the domain model a bit flatter. We don't really need to specialize BirthPlace
but we do need to indicate if it is special. We can add a property to the object (some people begrudge properties on domain objects, but I don't, since it makes queries easier, especially with LINQ) to indicate if it is special. Then we can get rid of the Contains
query altogether:
public class BirthPlace
{
public BirthPlace(String name, Boolean isSpecial = false)
{
Name = name;
IsSpecial = isSpecial
}
public String Name { get; private set; }
public Boolean IsSpecial { get; private set; }
}
public class Person
{
public static readonly DateTime ImportantDate;
public BirthPlace BirthPlace { get; set; }
public DateTime BirthDate
{
get; private set;
}
public void CorrectBirthDate(IRepository<BirthPlace> birthPlaces, DateTime date)
{
if (BirthPlace != null && date < ImportantDate && BirthPlace.IsSpecial)
{
BirthPlace = birthPlaces.GetForDate(date);
}
}
}
I think the statements "I need to reflect this change in the user interface" and "It's an important domain rule which I need to capture" describe two different problems. Clearly, the first one needs to be solved; it isn't clear that the second one does.
If other parts of your domain model need to know about changes here, you would do well to have a look at Domain Events (for example, Udi Dahan's implementation). You could also use this to set the BirthPlace property when the BirthDate gets set, even asynchronously if it is a potentially lengthy operation.
Otherwise, let's just look at the UI issue. First of all, in my domain model, I would have each entity abstracted as an interface. If you don't, then you may need to at least make some properties virtual
. I'd also be using a layer of abstraction for generation/returning my entities, such as IoC/factory/repository. I consider this layer to be outside the bounds of the domain model itself.
Now, we need a mechanism to notify the UI of changes to properties in domain entities, but of course the domain model itself is in a sense a closed system: we don't want to introduce new members or behaviours to satisfy the needs of any outside concern.
What if we decorate the entity in question with an implementation that implements INotifyPropertyChanged
? We could do this in our repository, which we've established is outside the bounds of the domain, so we would not be modifying the domain model itself, only using composition to wrap the entities with functionality that the system outside the domain model needs. To restate, the recalculation of BirthPlace
remains a concern of the domain model, while the UI notification logic remains a concern outside of the domain model.
It would look something like this:
public class NotifyPerson : IPerson, INotifyPropertyChanged
{
readonly IPerson _inner;
public NotifyPerson(IPerson inner) // repository puts the "true" domain entity here
{
_inner = inner;
}
public DateTime BirthDate
{
set
{
if(value == _inner.BirthDate)
return;
var previousBirthPlace = BirthPlace;
_inner.BirthDate = value;
Notify("BirthDate");
if(BirthPlace != previousBirthPlace)
Notify("BirthPlace");
}
}
void Notify(string property)
{
var handler = PropertyChanged;
if(handler != null) handler(this, new PropertyChangedEventArgs(property));
}
}
If not using interfaces, you would simply inherit from Person
and override the BirthDate
property, calling members on base
instead of _inner
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With