Validation in a Domain Driven Design
Asked Answered
N

5

67

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?

Niveous answered 5/2, 2009 at 16:20 Comment(0)
U
39

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;
    }
}
Urceolate answered 13/6, 2009 at 13:28 Comment(6)
The approach described in Bogard's article looks pretty handy. Thx.Niveous
One thing though, shouldn't the line return BrokenRules(entity).Count() > 0 really be return BrokenRules(entity).Count() <= 0 ?Chibchan
I just looked at Jimmy's post and basically put together a quick test locally, and I'd say the one thing I do not like about this approach is if you implement it using extension methods like in his article, when a consumer types "entity.", there are TWO Validate() methods that come up. One that is sitting on the entity to implement the IValidatable<T> interface, the other is the extension method. Is I were consuming an entity with two .Validate() methods, I probably would not know which one to call without looking into each method.Winifredwinikka
For entity.Customer we will have to create CustomerValidator?Inexcusable
Doesn't this break the rule that domain entities must always be valid?Earthquake
this is not ddd Approach and wrong at all. This kind of validation could ok for some DTO which can be in any state, but not for domain entity. In DDD validation in constructors is a must.Tomi
H
66

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

Henni answered 6/2, 2009 at 22:29 Comment(6)
I've always thought the advice from Greg Young that you posted is in no way practical in the real world.Contrail
He's not just advocating it because it sounds nice, he has very large systems in this mindset.Henni
Imagine there is an object incapsulating a List<> of things - Things. On the next stage of processing, after it has been filled, I have to ensure that the list has certain element and it is the only element of it's kind. How can I make Things not getting into invalid state? As a possible solution I thought to demand the user supply this object as the first element of the list, but I cannot do that. So the object Things can be in it's transitioning state in which it could stay till the beginning of the processing on the next stage. That is why I have to validate it before processing.Grandnephew
I also tend to think that a lot of business logic is just validation (ie. before we change some state, can this operation be done?). DDD is for complex domain. Sometimes the complexity is simply complex validation on that domain. You take that out, and you've got an anemic domain entity and you will have to look elsewhere to find the rules. Maybe it is justified later in pulling some of the rules out if the entity gets too big. But that can be done later (to avoid premature over optimization). Or perhaps consider remodeling the aggregate.Heilungkiang
Specifications seem to be more for needs that the entity has but can't fulfill in it's own domain. Instead of introducing bi-directional access to the aggregate with the outside world, the entity can just provide a specification. A domain service can take that and fulfill it (perhaps making repository calls) and return something like a Value Object. The VO can then be passed into a contruction/method on the entity to fulfill required need.Heilungkiang
@Pixar These are 2 different lists! The 1st list is with invariant any element is ok, the 2nd list is with invariant has certain element and it is the only element of it's kind. On the next stage of processing, after the 1st list has been filled, a special business method tries to fill the 2nd list from the 1st one. Neither of the lists gets into invalid / transitioning state!Warfeld
U
39

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;
    }
}
Urceolate answered 13/6, 2009 at 13:28 Comment(6)
The approach described in Bogard's article looks pretty handy. Thx.Niveous
One thing though, shouldn't the line return BrokenRules(entity).Count() > 0 really be return BrokenRules(entity).Count() <= 0 ?Chibchan
I just looked at Jimmy's post and basically put together a quick test locally, and I'd say the one thing I do not like about this approach is if you implement it using extension methods like in his article, when a consumer types "entity.", there are TWO Validate() methods that come up. One that is sitting on the entity to implement the IValidatable<T> interface, the other is the extension method. Is I were consuming an entity with two .Validate() methods, I probably would not know which one to call without looking into each method.Winifredwinikka
For entity.Customer we will have to create CustomerValidator?Inexcusable
Doesn't this break the rule that domain entities must always be valid?Earthquake
this is not ddd Approach and wrong at all. This kind of validation could ok for some DTO which can be in any state, but not for domain entity. In DDD validation in constructors is a must.Tomi
V
6

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.

Vidda answered 6/2, 2009 at 22:21 Comment(0)
J
0

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<string> 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);
    }
}
Janenejanenna answered 12/3, 2018 at 21:14 Comment(0)
O
-2

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<Document> _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.

Oven answered 23/8, 2016 at 1:12 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.