views:

1122

answers:

3

How do you deal with validation on complex aggregates in a domain driven design? Do you consolidate your business rules/validation logic?

I understand argument validation. And I understand property validation which can be attached to the models themselves and do things like check that an email address or zipcode is valid or that a first name has a minimum and maximum length.

But what about complex validation that involves multiple models? Where do you typically place these rules & methods within your architecture? And what patterns if any do you use to implement them?

+3  A: 

I usualy use a specification class, it provides a method (this is C# but you can translate it in any language) :

bool IsVerifiedBy(TEntity candidate)

This method performs a complete check of the candidate and its relations. You can use arguments in the specification class to make it parametrized, like a check level...

You can also add a method to know why the candidate did not verify the specification :

IEnumerable<string> BrokenRules(TEntity canditate)

You can simply decide to implement the first method like this :

bool IsVerifiedBy(TEntity candidate)
{
  return BrokenRules(candidate).IsEmpty();
}

For broken rules, I usualy write an iterator :

IEnumerable<string> BrokenRules(TEntity candidate)
{
  if (someComplexCondition)
      yield return "Message describing cleary what is wrong...";
  if (someOtherCondition) 
      yield return
   string.Format("The amount should not be {0} when the state is {1}",
        amount, state);
}

For localization, you should use resources, and why not pass a culture to the BrokenRules method. I place this classes in the model namespace with names that suggest their use.

Think Before Coding
+4  A: 

Instead of relying on IsValid(xx) calls all over your application, consider taking some advice from Greg Young:

Don't ever let your entities get into an invalid state.

What this basically means is that you transition from thinking of entities as pure data containers and more about objects with behaviors.

Consider the example of a person's address:

 person.Address = "123 my street";
 person.City = "Houston";
 person.State = "TX";
 person.Zip = 12345;

Between any of those calls your entity is invalid (because you would have properties that don't agree with each other. Now consider this:

person.ChangeAddress(.......);

all of the calls relating to the behavior of changing an address are now an atomic unit. Your entity is never invalid here.

If you take this idea of modeling behaviors rather than state, then you can reach a model that doesn't allow invalid entities.

For a good discussion on this, check out this infoq interview: http://www.infoq.com/interviews/greg-young-ddd

Ben Scheirman
I've always thought the advice from Greg Young that you posted is in no way practical in the real world.
Ian P
He's not just advocating it because it sounds nice, he has very large systems in this mindset.
Ben Scheirman
+5  A: 

I like Jimmy Bogard's solution to this problem. He has a post on his blog titled "Entity validation with visitors and extension methods" in which he presents a very elegant approach to entity validation that suggest the implementation of a separate class to store validation code.

public interface IValidator<T>
{
    bool IsValid(T entity);
    IEnumerable<string> BrokenRules(T entity);
}

public class OrderPersistenceValidator : IValidator<Order>
{
    public bool IsValid(Order entity)
    {
        return BrokenRules(entity).Count() > 0;
    }

    public IEnumerable<string> BrokenRules(Order entity)
    {
        if (entity.Id < 0)
            yield return "Id cannot be less than 0.";

        if (string.IsNullOrEmpty(entity.Customer))
            yield return "Must include a customer.";

        yield break;
    }
}
David Negron
The approach described in Bogard's article looks pretty handy. Thx.
Todd Smith
+1 Great solution
Roberto Sebestyen
@David One thing though, shouldn't the line `return BrokenRules(entity).Count() > 0` really be `return BrokenRules(entity).Count() <= 0` ?
Roberto Sebestyen