C# Domain-Driven Design – Best Validation Approaches for DDD Purists

cdomain-driven-designnhibernate

I recently asked this question: Validation inside Constructor

I am trying to decide where to put the validation in a DDD app. I believe it should be done at every layer.

I am now concentrating on the Domain model. I was expecting validation to go in the Setter methods like this: https://lostechies.com/jimmybogard/2016/04/29/validation-inside-or-outside-entities/

What is the "best" way to do validation. I am talking from the perspective of a DDD purist. I realise that the "best" way may not always be the most practical way in every situation.

Also, I am not necessarily saying that DDD is always the best approach to solve a problem. I am just trying to imporve my thinking in this specific area.

I believe the options are:

1) Validation inside setters as described here: https://lostechies.com/jimmybogard/2016/04/29/validation-inside-or-outside-entities/

2) IsValid method as described here: http://enterprisecraftsmanship.com/2016/09/13/validation-and-ddd/

3) Check validity in application services layer as described here: http://enterprisecraftsmanship.com/2016/09/13/validation-and-ddd/

4) TryExecute pattern as described here: http://enterprisecraftsmanship.com/2016/09/13/validation-and-ddd/

5) Execute/CanExecute pattern as described here: http://enterprisecraftsmanship.com/2016/09/13/validation-and-ddd/

I am using NHibernate so the setters on the domain objects must have a visibility of: protected set.

Best Answer

It's an interesting question that seems to come up in a variety of guises.

I am of the opinion that the best approach is to allow the concept of an object that is a invalid state.

The reason being that the validation rules for an object are usually not set in stone. They may change over time, or be different for different operations. Thus an Object which you created, populated and persisted some time ago, may now be deemed invalid.

If you have setter or constructor validation checks, then you have a big problem in that your application will error when you try to retrieve these entities from your database, or reprocess old inputs etc.

Additionally I don't think that business rules incorporate simple yes/no validation for the most part. If your domain is selling cakes and you don't deliver south of the river, but someone offers you a million pounds to do it. Then you make a special exception.

If you are processing millions of applications and you have strict rules about what characters can be in a field, then you probably have a process for correcting bad fields. You don't want to be unable to accept a bad field at all, it just follows a different path through the Domain.

So, if in the code you are so strict that 'invalid' data can just never exist because the constructor would throw an exception, you are bound to be brittle and fail for questions like "how many people filled the form in wrong?"

Allow the data and fail the operation. This way you can adjust the data or rules for the operation and re run it.

example:

public class Order
{
    public string Id {get;set;}
    public string Address {get;set;}
    public void Deliver()
    {
        //check address is valid for delivery
        if(String.IsNullOrWhiteSpace(this.Address))
        {
            throw new Exception("Address not supplied");
        }

        //delivery code
    }
}

So here we are unable or don't wish to deliver to blank addresses. The Domain object Order, allows you to populate a blank address but will throw a exception if you attempt to deliver that order.

An application, say a queue worker processing orders from json data stored in a queue, coming across an 'invalid' order:

{
    "Address"  :""
}

Is able to create the Order object, as there are no validation checks in the constructor, or the setter for Address. However, when the Deliver method is called it will throw the exception and the application will be able to take an action. eg

public class QueueWorker
{
    public void ProcessOrder(Order o)
    {
        try
        {
            o.Deliver();
        }
        catch(Exception ex)
        {
            Logger.Log("unable to deliver order:${o.Id} error:${ex.Message}");
            MoveOrderToErrorQueue(o);
        }
    }
}

The Application can still work with the Invalid Order, moving it to the error queue, accessing its Id, reporting on errors etc. But the Deliver Operation contains the Domain logic of how you want to handle Delivery to blank addresses.

If the Domain logic later changes with further requirements:

public class Order
{
    public string Id {get;set;}
    public string Address {get;set;}

    public void Deliver()
    {
        //check address is valid for delivery
        if(String.IsNullOrWhiteSpace(this.Address))
        {
            throw new Exception("Address not supplied");
        }
        if(Address.Contains("UK"))
        {
            throw new Exception("UK orders not allowed!");
        }
        //delivery code

    }
}

Then you can still process orders on the queue which were generated when UK addresses were allowed and get the expected result.

Its interesting to compare my answer with the one from @VoiceOfUnReason.

I've used a string Address for simplicity and also because there is no absolute definition of what an address is. But if we have a more absolute definition, say his Deposit always has a currency. It's nonsensical to even talk about a deposit with no currency.

In that case yes, you can define a new value type which simply cant exist unless you have specified the currency and loads of potential errors in your code will simply not be possible.

But you have to be sure its a fundamental thing. Otherwise you are asking for trouble later!