Question

I have read the book professional asp.net design patterns by scott millett and in his example he validates his business logic within a Validate() method and if any broken rules are broken they get added to a collection and the service layer calls a method on the model called GetBrokenRules().

Now I have also read several books, blogs and forums regarding DDD and it says that in DDD an object should never get into an invalid state.

All the examples I have seen regarding DDD throw errors when a business rule has been broken instead of passing back a collection of broken rules. I have even downloaded the latest source code by scott millett and he has now changed his code which now throws errors instead of passing back the list of broken rules. I have also seen the same approach with other DDD code samples.

I am having a debate with a team member who believes that throwing errors is resource expensive and we should NOT throw the error but return a collection of broken rules like we currently do. However by doing this we are passing around an object that is invalid because it has dodgy data and we only check for its broken rules at the end.

I was just thinking what other peoples opinons are on this subject. Should we throw an error as soon as a business rule fails? If so, can you highlight any pros and cons of doing so. I don't know how resource expensive throwing errors are in .net so I can not argue against that point but I was wondering if this is also a matter of personal opinoin instead of coding standards.

Mike

Was it helpful?

Solution

What @Oded said is perfectly valid.

However there is another way to handle the validation, without throwing exceptions and without passing around an invalid object. Simply put, you always create the object via a factory method. If everything is good, an isntance is returned otherwise it returns null. Of course, this means you have to check for null once when you creat the object but that's about it. The factory method (which can be part of a service) can take as argument an IValidationDictionary (not a BCL interface) on which the factory can set the errors. But we talk here only about input data (formatting validation) from the user. No business rules here. If the object will be used in a context in which its state is invalid then it has to throw an exception.

You can avoid that 99% by having a method on the object or on its aggregate root like CanDoThat(). If object.CanDoThat(context) do that and avoid an exception. The exception may be triggered though if because of concurrency things change in the mean time.

I usually have the following workflow:

  • user input validation at the controller layer (which is the anti corruption layer for the app). Only data formatting is handled here.
  • Once data is valid is send forward to be processed (usually via a command handler)
  • When processing I ask the aggregate root if it can do a specific action. If it can everything moves forward.
  • If can not then there are 2 cases:

    1. It is probable that something is wrong like a bug so an exception is appropriate here (that's 99% cases).
    2. It's a valid response in that context so it's simply ignored.
  • Of course you may have the case where everything is ok at the aggregate root level, but it fails at the repository level because of a db constraint. This means the repository throws an exception, which I handle (because I exepct that case to happen). If the user expects a response then I'll throw a specific exception to signal the error which will set the View's ModelState.

Now this is a tricky situation, because it looks like I'm using exception to control the code flow but that's not the case. While I expect the db to throw a constraint violated exception, it's still an exceptional case because that doesn't happen every time and it's not a situation the user can avoid. I'm throwing next the specific exception because the first one is something persistence related, but my controller doesn't know about it so I have to communicate that exceptional state in a way it understands.

Ok, long anwser but the idea is that some errors, while expected ar still exceptions and they need to be treated as such. However if there is a formatting validation error on ipnut data or the input data is invalid in a specific way, that should be handled in the first anti corruption layer, the controller itself and no exceptions are needed. The controller should not let invalid input go forward. If it goes forward then it is a bug, an exceptional situation.

Hope, I wasn't very confusing :)

OTHER TIPS

Exceptions are only expensive when they are actually thrown.

Since you should be validating logic beforehand, exceptions should be few and far between. If this is not the case, you are doing it wrong.

Exceptions are by far the best mechanism to notify that a model is in an invalid state. They require explicit handling if the application is not to terminate.

Using error collections/return values is not a good practice as these can (and many times do) get ignored by the using code.

Very good discussion. It wakes old discussion subjects back to life. I have tested several approaches and found that a mix can be a good alternative. I use exceptions when validating input parameters to service methods or ctor. I use Validators together with Visitor Pattern to examine domain entities IF the validation knowledge is not part of the entity responsibility. If you are part of the Persistence-ignorance-religion (which I am) you can use validators that are located in infrastructure layer and validate if it can be persisted (or perhaps exported to another system through an adapter...). But however I use very little public auto properties on my entities just for making it difficult putting the entity in a invalid state. I also let the entity validate itself regarding it's own domain scope/responsibility (trying to follow SRP here...).

Throwing exceptions as a general solution for notify other parts/layers may not be optimal for every case, but maybe for some.

/Cheers

Why not throw an exception with the list of broken rules?

Compared to the cost of constructing the rules, figuring out what to do with them, telling the user about the error, and all the network traffic in between, an exception costs almost nothing.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top