Validation in a Domain Driven Design

ValidationDomain Driven-Design

Validation Problem Overview


How do you deal with validation on complex aggregates in a domain driven design? Are you consolidating 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?

Validation Solutions


Solution 1 - Validation

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

Solution 2 - Validation

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;
    }
}

Solution 3 - Validation

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.

Solution 4 - Validation

Multiple model validation should be going through your aggregate root. If you have to validate across aggregate roots, you probably have a design flaw.

The way I do validation for aggregates is to return a response interface that tells me if validation pass/fail and any messages about why it failed.

You can validate all the sub-models on the aggregate root so they remain consistent.

// Command Response class to return from public methods that change your model public interface ICommandResponse { CommandResult Result { get; } IEnumerable Messages { get; } }

// The result options
public enum CommandResult
{
    Success = 0,
    Fail = 1
}

// My default implementation
public class CommandResponse : ICommandResponse
{
    public CommandResponse(CommandResult result)
    {
        Result = result;
    }

    public CommandResponse(CommandResult result, params string[] messages) : this(result)
    {
        Messages = messages;
    }

    public CommandResponse(CommandResult result, IEnumerable<string> messages) : this(result)
    {
        Messages = messages;
    }

    public CommandResult Result { get; private set; }

    public IEnumerable<string> Messages { get; private set; }
}

// usage
public class SomeAggregateRoot
{
    public string SomeProperty { get; private set; }


    public ICommandResponse ChangeSomeProperty(string newProperty)
    {
        if(newProperty == null)
        {
            return new CommandResponse(CommandResult.Fail, "Some property cannot be changed to null");
        }

        SomeProperty = newProperty;

        return new CommandResponse(CommandResult.Success);
    }
}

Solution 5 - Validation

This questions a bit old now but in case anyone is interested here's how I implement validation in my service classes.

I have a private Validate method in each of my service classes that takes an entity instance and action being performed, if validation fails a custom exception is thrown with the details of the broken rules.

Example DocumentService with built in validation

public class DocumentService : IDocumentService { private IRepository _documentRepository;

    public DocumentService(IRepository<Document> documentRepository)
    {
        _documentRepository = documentRepository;
    }

    public void Create(Document document)
    {
        Validate(document, Action.Create);

        document.CreatedDate = DateTime.Now;

        _documentRepository.Create(document);
    }

    public void Update(Document document)
    {
        Validate(document, Action.Update);

        _documentRepository.Update(document);
    }

    public void Delete(int id)
    {
        Validate(_documentRepository.GetById(id), Action.Delete);

        _documentRepository.Delete(id);
    }

    public IList<Document> GetAll()
    {
        return _documentRepository
            .GetAll()
            .OrderByDescending(x => x.PublishDate)
            .ToList();
    }

    public int GetAllCount()
    {
        return _documentRepository
            .GetAll()
            .Count();
    }

    public Document GetById(int id)
    {
        return _documentRepository.GetById(id);
    }

    // validation 

    private void Validate(Document document, Action action)
    {
        var brokenRules = new List<string>();

        if (action == Action.Create || action == Action.Update)
        {
            if (string.IsNullOrWhiteSpace(document.Title))
                brokenRules.Add("Title is required");

            if (document.PublishDate == null)
                brokenRules.Add("Publish Date is required");
        }

        if (brokenRules.Any())
            throw new EntityException(string.Join("\r\n", brokenRules));
    }

    private enum Action
    {
        Create,
        Update,
        Delete
    }
}

I like this approach because it allows me to put all my core validation logic in one place which keeps things simple.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionTodd SmithView Question on Stackoverflow
Solution 1 - ValidationBen ScheirmanView Answer on Stackoverflow
Solution 2 - ValidationDavid NegronView Answer on Stackoverflow
Solution 3 - ValidationthinkbeforecodingView Answer on Stackoverflow
Solution 4 - ValidationTodd SkeltonView Answer on Stackoverflow
Solution 5 - ValidationJason WatmoreView Answer on Stackoverflow