7

I'm writing a VB.NET Winforms project based on MVVM (using Winforms binding). My instinct is to never allow a domain entity be in an invalid state. This requires that I do validation checks in the constructor for new entities and in each setter for existing entities:

Public Class Product


    Public Sub New(ProductID as Integer, Name as String)

        If ProductID > 0 AndAlso Name.Length > 5 Then

            _ProductID = ProductID
            _Name = Name

        Else
            Throw New InvalidProductException
        End If
    End Sub

    Private _ProductID as Integer
    Public Property ProductID as Integer

        Set(value as String)

            If value > 0 then
                _ProductID = value
            Else
                Throw New InvalidProductException
            End If

        End Set

    End Property

    'Same principle as above for Name setter.


End Class

Then I ran across Data Annotations, which seemed pretty slick. I noticed that most people using Data Annotations allow the domain entity to become invalid temporarily, and then validate the entity at some later point with a call to Validate.ValidateObject. By this point the entity is invalid and the original state has been lost unless you have some other mechanism to roll it back.

Two Questions:

1) Do you allow domain entities to become temporarily invalid?

2) Based on your answer to #1, what techniques do you use to validate the entity?

Casey Wilkins
  • 2,555
  • 2
  • 23
  • 31

3 Answers3

6

Your instinct is right. In DDD objects should never be allowed to enter a state that is not valid from domain perspective. Even temporarily. Objects should protect their internal invariants, this is pretty basic OOP. Otherwise it would not be an object but just a dumb data container. Often times people get confused by the UI frameworks or they overgeneralize the term 'validation'.

For example every Product in your system should have SKU. Or every Customer should have social security number. Enforcing these rules is the direct responsibility of Product and Customer entities. Good old ArgumentNullException will let client code realize that it broke some invariant. Enforcing this rule is not a responsibility of UI or some abstract 'Validator'. If you let this rule be enforced outside your entity you will:

  • eventually end up with invalid state which can result in crash or will require you to write some compensation code to avoid this crash

  • more importantly, you will not be able to reason about the Product by just reading Product code

Furthermore business rules are usually more complex than that so it would be even harder to enforce them outside of the entity without breaking its encapsulation. There is another group of rules that can be easily enforced by using DDD Value Object. In the above example you would create class 'SKU' and 'SocialSecurityNumber'. These classes will be immutable and will enforce all the formatting rules. They can also have static methods like:

SocialSecurityNumber.TryParse(String untrustedString, out SocialSecurityNumber)

or

SocialSecurityNumber.IsStringValid(String untrustedString)

UI can use these methods to validate user input. There is no reason for UI to 'break' objects even temporarily. If you let this happen you will end up with Anemic Domain Model. Unfortunately a lot of sample code on the internet promotes this approach. The bottom line is that your validation rules are coming from your domain and they should be enforced by domain objects.

Dmitry
  • 17,078
  • 2
  • 44
  • 70
  • 1
    As you pointed out I think the confusion comes from a lot of examples that allow a domain object to be invalid. I'm still not quite sure how you would use Data Annotations without allowing the object to be invalid temporarily, but that is beside the point I guess. Thanks for the input. – Casey Wilkins Aug 17 '11 at 17:58
  • 1
    @Casey: well, I've hardly seen any example that shows DDD in a right way. DDD is supposed to be used for complex business scenarios and most of the examples are pretty simple and focus on what an entity or value object is, while the hardest part is actually how to define bounded context, model your aggregates and keep them valid and consistent in a way that doesn't cross their boundaries. – kstaruch Aug 17 '11 at 18:40
5

Firstly, no, domain objects should never exist in an invalid state.

I think the confusion you are having stems from the fact you are trying to power your UI screens from domain objects (and you are definitely not the only one), i.e. binding to your domain objects properties. You shouldn't. Your UI should get it's data from the properties of a view model; a purpose built UI object, which is allowed to be in an invalid state, and can make use of any cool UI validation frameworks.

Domain objects should only come into play in the context of a command/transaction. i.e. the user has selected a view model object from a list on the screen and wishes to perform an action on it. Typically the UI will then call an application/command service method for the action they wish to perform, passing the ID from the view model the user has selected. This will then retrieve the domain object from it's repository and invoke the appropriate method.

In terms of where the view model object comes from: I have a separate query service that the UI calls. This provides 'flat' (de-normalised aggregated data) DTO's which can be used to populate view model objects or act as the view models themselves. This service is unaware of any domain model and simply returns projections from the data that the domain objects conduct transactions over.

I can't recommend enough reading up on CQRS, even if you only take up certain aspects of it. Not only does it help make practical sense of DDD, but if implemented well it can really help with the performance aspect of getting the data to your UI screens.

David Masters
  • 8,069
  • 2
  • 44
  • 75
  • I do make use of ViewModels. The application is a desktop application that basically has one tier, so the ViewModels are tied to the Domain objects. In other words, when a user changes a text box from the UI, the ViewModel receives that input through binding and then immediately changes the Domain Object. That pretty much means the ViewModel has to be valid at all times if the Domain Object is going to be valid. I supposed I could change my ViewModel to only update the Domain Object when some command, like "Save" is invoked. – Casey Wilkins Aug 18 '11 at 21:36
  • 1
    OK, so you are indirectly binding to your domain objects....Your last sentence is what I'd recommend. When the user clicks something to perform an action, you take the data that has been input into the view model and invoke a method on your domain object. The view models shouldn't be tied to your domain objects. – David Masters Aug 19 '11 at 08:02
3

No, in my opinion domain entities should never be allowed to be invalid, even temporarily. The problem is that it if you allow the domain to be invalid, just like you described in your question, it gets difficult to introduce new rules as complexity grows. For example you allow entity to be invalid due to some attribute, assuming that it will be validated later. But before that happens someone adds another rule, that varies its result in accordance to the same attribute - how do you know if the rule behaves correctly? You don't. Believe me, it happens quite often in non trivial domains.

The other reason for nor allowing state to be invalid is that in certain scenarios it can introduce problems with ORMs - I have personally seen an issue involving NHibernate cache and sub-entities that were invalid but somehow still remained in cache, I can't recall any specific details though.

The technique I tend to use bases on validation rules and validation results. In short, most of the methods on entities is implemented in the following way (C#, if you don't mind):

        public virtual void ChangeClaimEventDate(DateTimeOffset newDate)
        {
            var operationResult = ValidatorOf<Claim>
                .Validate()
                .WithCriticalRuleOf<EventDateFallsIntoPolicyCoverage>().WithParam(newDate)
                .WithCriticalRuleOf<EventDateFallsIntoInsuredCoverage>().WithParam(newDate)
                .WithCriticalRuleOf<PerformedServicesAreAvailableOnEventDate>().WithParam(newDate)
                .WithCriticalRuleOf<EventDateCannotBeChangedForBilledClaim>().WithParam(newDate)
                .ForOperation(this);

            if (operationResult.OperationFailed)
            {
                throw new InvalidBusinessOperation(operationResult);
            }

            SomeDate = newDate;
        }

The most important thing about this code, is that certain validation rules are checked even before the entity is changed. This example shows usage of result sets, as very often I need to provide information about validation even if it succeeds (in other words, I have validations that fail and information about it has to be shown to user; however the domain entities are still valid.

The OperationResultSet and ValidatorOf are pretty simple infrastructure classes that allow adding new validators easily with fluent interface. The validators are implemented as classes implementing IValidator interface, which allows implementation of pretty complex validation rules and it is easier to test them individually as well.

My point is, that validation should be performed before the changes to domain entities are performed - with the right convention and some infrastructure it even simplifies the code structure.

Edit note: due to some critical voices for this answer, I've decided to change the sample code to one that throws an Exception instead of returning results. Although I still believe that it is the way to go for my kind of scenarios, I agree that without specifying full context this might be misleading - the Exceptions should be indeed the first option and additional factors should exist to choose alternatives.

kstaruch
  • 1,289
  • 7
  • 7
  • I agree with you and I really, really like this approach. Could you possibly post a short code example for the ValidatorOf class? – Casey Wilkins Aug 17 '11 at 16:44
  • 1
    @kstaruch: This is a matter of personal preference, but do you think that you might be overgeneralizing validation? Why writing so much code if you can simply check the rule internally and throw exception? And have another method like IsOkToChangeSomeDate so that UI can check upfront? – Dmitry Aug 17 '11 at 17:07
  • @Casey: I'm sorry, but I can't due to legal issues - simply put, although I wrote it, I'm not the owner of the code. But it's fairly simple actually, ValidatorOf is a fluent interface for gathering Validators and invoking Validate method with parameters provided by WithParam and ForOperation ('this' is an instance of MyEntity). ForOperation gathers results from all Validate methods and merges them into single result set. – kstaruch Aug 17 '11 at 18:17
  • @Dmitry: I've tried your approach as well. The domain that I'm dealing with is pretty complex and in most real-life cases I have like 5-10 validation rules for a single business operation. Most of them consist of multiple checks, so testing them in isolation with mocked entities is a big advantage for me. Plus very often I need to return meaningful text for every rule in scope of operation, so exceptions are not an option for me. Besides every rule is a single class, so I can reuse them where I want (incl. UI). – kstaruch Aug 17 '11 at 18:28
  • @Dmitry: Of course that is the way I handle my domain, which is fairly complex one. The most important is to keep the aggregates valid, so in simpler cases your approach is ok as well. – kstaruch Aug 17 '11 at 18:32
  • @kstaruch: I would argue that just using words like 'Validator', 'Rule' and 'Data' in DDD code maybe an overgeneralization. These are not words from Ubiquitous language, they are too generic. Its not about complexity its about approaching problem from the "generic data validation" angle. Again, I don't know enough about your domain to make a good example. – Dmitry Aug 17 '11 at 18:56
  • @Dmitry: of course these are not words from domains Ubiquitous language - these are simply infrastructural concepts used to translate UL to technical solution. They are not visible out of domain. For example the name of every class that implements validation rule is expressed in UL. Saying that these words are not part of UL is exactly like saying that Exception is not part of UL - of course it isn't. And believe me, most of the rules that maintain consistency and validity have nothing to do with simple "generic data validation". – kstaruch Aug 17 '11 at 19:19
  • @kstaruch: Am I understanding you correctly that all business logic is expressed as a set of 'validatable rules'? And that consumers of your domain deal with things like generic 'OperationResultSet'? Please read 'business rules' section of this article it is exactly what we are talking about. http://www.infoq.com/articles/ddd-in-practice – Dmitry Aug 17 '11 at 19:47
  • Even though all the domain specific business rules should be encapsulated in the domain layer, some application designs put the rules in facade classes, which leads to domain classes becoming "anemic" in terms of business rules logic. This may be an acceptable solution in small size applications, but it is not recommended for mid-size to large enterprise applications that contain complex business rules. A beter design option is to put the rules where they belong, inside the domain objects. If a business rule logic spans two or more Entity objects, then it should become part of a Service class. – Dmitry Aug 17 '11 at 19:49
  • @Dmitry: I don't know why you assume that I express all business logic as validable rules. What kind of domain would contain only validations? Besides, the question was not about expressing all business logic. And yes, consumers of my domain (in my case the consumers are SOA web services mostly) are dealing with OperationResultSets which state if operation was successful or not. In your example, consumers deal with .NET Exceptions when operation failed. Is it different? I think that this discussion is no longer related with the original question, so if you want we should move it elsewhere. – kstaruch Aug 17 '11 at 20:07
  • 1
    I just thought I'd add that I don't like this approach as it requires methods that change state as well as returning data (http://martinfowler.com/bliki/CommandQuerySeparation.html). Also, the question is tagged DDD, and I don't believe this to be a DDD approach. Exceptions should be thrown when an attempt is made to invalidate the state of an entity. – David Masters Aug 19 '11 at 08:45
  • @David Masters: although CQS is just one of many concepts how to build applications, I really don't think that this approach violates it - there is no data returned. OperationResult contains only information whether action was successful or not. Exception is a technical concept, there's no rule stating that in DDD you have to throw exceptions. In my scenario Exceptions were not the best approach and were replaced by operation results which led to cleaner code (due to structure of application). The question was "what do you use" not "what's best" - there is no "general best" solution. – kstaruch Aug 20 '11 at 11:29
  • @kstaruch, this approach does voilate the CQS approach as it returns something and changes state, that's it's only requirement not to do. Also, CQS aside, I personally feel this is a 'code smell' for the reasons given in this answer, but most importantly point 4: http://stackoverflow.com/questions/4670987/why-is-it-better-to-throw-an-exception-rather-than-return-an-error-code – David Masters Aug 22 '11 at 07:59
  • @Dmitry - after second thought I've decided to edit my answer - I agree that I didn't provide enough context to use results instead of Exceptions and this might blur the actual goal of answer. Indeed in general Exceptions should be used - for my purpose I found alternative approach to be more suited but there are additional factors involved and I failed to inform about it in original answer. So thanks for the input. – kstaruch Aug 22 '11 at 23:30
  • @David - thanks for the comments as well (see previous comment addressed for Dmitry). – kstaruch Aug 22 '11 at 23:32