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!
Best Answer
Before I address the kinds of considerations that go into object creation, let's first address the motivation behind your question: the phrase "Always Valid Domain Entities". This phrase is, at best, misleading, and makes little sense in the context of DDD. This should be apparent for two related reasons:
The first is that it implicitly shifts your focus away from the behavior of the system, instead, asking you to consider validation only in terms of state. While superficially this may seem to make sense (of course validation is in terms of state!), you must remember that the fundamental principal of DDD is that a system is modeled according to behavior. The motivation for this is quite simply that the context, or the business process itself, is often an important consideration when determining whether or not some piece of state is valid. Modeling a system in this way can greatly reduce it's complexity.
This brings us to the second reason, which is in regard to the practical requirements such a system would entail. In order to create a system of "Always Valid Domain Entities", it would require one to model every single permutation of state according to the business processes in which the state is used. A simple example can illustrate the limitations of this:
Rules:
Customer
must be over 18 to registerCustomer
must be under 25 to qualify for discount on registrationCustomer
must be over 25 to make reservationThe first thing you should notice is that all of these rules (like nearly all rules) apply to some business process. They don't exist in a vacuum. These rules would be validated on
customer.Register()
andcustomer.Reserve()
. This results in a much more descriptive and declarative paradigm because it's clear where rules are executing.If we wanted to model these rules such that it results in system of "Always Valid Domain Entities" we would need to partition our
Customer
intoRegistrar
andReserver
entities. And while that might not seem so bad for this example, as rules become more complex and abundant you will end up with an explosion classes like this that represent state "within" some context or process. This is simply unnecessary and will inevitably create issues when two such objects are dependent on the same slice of state.Additionally, something like
Customer c = new Customer()
is a bad place to throw an exception because it's unclear what business rules might apply. Which brings us to our final discussion.I am just going to come out and go as far to say that there should be zero validation of business rules happening in constructors. Object construction has nothing at all to do with your business domain, and for that reason, in addition to the reasons above concerning context and coherence, all business rules should be enforced within an entity's method bodies (it's likely methods are named after business processes right?).
Furthermore, "newing" up an object is not the same thing as creating a new entity in your domain. Objects don't come out of nowhere. If there are business rules regarding how a new entity can come into your system, then it should be modeled in your domain. Here is some further discussion on the topic by a true master http://udidahan.com/2009/06/29/dont-create-aggregate-roots/