7

EDIT: Thanks for all the great answers! In reading these responses I realized that I'm approaching this from the wrong angle so I wrote a new question here.

I had this discussion with my superior about whether validation rules should be in the domain model or not (specifically, in the property accessors) and I tend to lean towards putting them just about anywhere else but there. In my opinion, put them in the UI and in the brokers/persistence layer.

The argument in favor of putting it in the domain is to 'protect the data' because data is king and long after the application code has been replaced the data will still be there. And also because "object oriented programming is about coupling data and behavior".

So take, for example, an object that represents a scheduled item with a StartTime and EndTime and a validation rule that says the StartTime must be before the EndTime (and the EndTime must be after the StartTime). Then while programming this you repeatedly run into issues as you realize things like:

  • Shoot, I forgot StartDate/EndDate could be equal too and my code errors on half the data it tries to load from the database

  • Ok so apparently the StartDate has to be set before the EndDate or my EndDate validation fails. Seems odd that I can't set properties like these in any order but ok.

  • Or... I could add more code to check if the StartDate is null and allow the EndDate to be set to anything so long as StartDate hasn't been set yet. Ok I'll do that.

  • But wait, unless I force the user to use a constructor with 10 parameters I can't absolutely ensure that there won't be any uninitialized properties because my validation logic will never get hit if they don't call the properties in the first place!

  • Poop, I tried forcing the consumer to use ridiculously verbose constructors but now I'm having serialization headaches. The DataContractSerializer that WCF uses totally ignores my constructor. I'm sure there's a way around this though, I'll figure it out.

  • Except now I want to use Sql Sessions and by default it uses an XmlSerializer that requires a parameterless constructor.

  • Uh oh, bad data has been imported into the database without going through my perfectly crafted domain model. Now my domain model prevents me from displaying this invalid data to the user so I have to correct it all myself. It would be great if I could jut allow them to correct it, but my domain model doesn't know how to do anything but crash when it reads invalid data.

So here's what I'm trying to figure out: are 'validation rules' really 'behaviors'? Technically they are, but in regard to the idea that "object oriented programming is data + behavior", does this really apply to validation?

4
  • This Question may help: programmers.stackexchange.com/questions/119778/…
    – JeffO
    Commented Oct 7, 2014 at 13:44
  • 1
    If you go beyond trivial validations (like null checking or basic range checking) you are probably right, they aren't behaviors and don't belong in the class. Often validations can be attached to a class through external metadata (annotations and/or data files) and can therefore be more easily manipulated, adjusted and applied at different levels (UI and database, for instance). Only validations required to ensure that the class is a valid object should be coded directly into the class.
    – Bill K
    Commented Oct 7, 2014 at 18:00
  • @JeffO I think my issue has more to do with where to draw the line between what you call an invalid object and a perfectly valid object with an invalid property. I don't think an invalid property 'necessarily' means the object itself is invalid. The simplest way I can explain this is that an employee might file a paper that's incorrectly filled out. He can pull that file out at any time to correct it. But in software I wouldn't be able to pull the file back out if my domain logic prohibited invalid property values... and I can't always force everyone to use my domain objects to save to the db.
    – BVernon
    Commented Oct 8, 2014 at 19:55
  • 1
    If it were me I would not allow setting the start time and end time individually. I would create a ChangeTime(starttime, endtime) function and expose the actual datetime fields as read only. Other than that, Id follow Amons advice. As it is now, it sounds like you're doing it wrong. Commented Oct 9, 2014 at 22:54

3 Answers 3

16

Programming isn't an exact science (at least not yet), so you have a lot of leeway which you should use to apply common sense and do the sensible thing.

Every object is responsible for itself

There is a concept more fundamental than object-oriented programming, and this is encapsulation. Encapsulation could be paraphrased as “every conceptual unit is responsible for itself”, where conceptual units can be of any scale (such as libraries, classes, methods, or even simple code blocks). Encapsulation is achieved by separating inner workings and implementation details from a public interface. It is really important to carefully choose what is part of the public interface, and what should be internal.

That part about methods and blocks also being eligible for encapsulation may be confusing, and I don't cover it in the rest of the post. It ends up meaning “declare variables in the narrowest possible scope, and don't use global variables”. This is not hypothetical. I have seen people return values from functions via global variables, and it was not pretty.

You shouldn't be able to reach invalid states

Object oriented programming is one popular strategy to make data responsible for themselves, by combining behaviour with these data. One crucially important responsibility is maintaining data consistency. That means a properly encapsulated object cannot be brought into an invalid state via the public interface, or conversely: everything that can be done via the public interface is valid. There are a number of steps to maintain consistency:

  • Don't allow inconsistent objects to be created in the first place. If necessary, use the builder pattern to describe temporarily inconsistent objects.
  • Don't allow data to be mutated in a way that makes them inconsistent.
    • Suggestion: Don't allow data to be mutated, at all.
  • Use static typing to prove consistency properties.
  • Use run-time validation to ensure consistency properties you cannot prove via the type system.

Static typing may seem like a no-brainer, but it's really important. When you declare fields or properties of an object, these usually have a specific type. If a certain field must contain a string, you can't put an integer there instead. The compiler then proves that your program is consistent with this requirement. However, all type systems are inherently limited, and cannot prove all properties of your program. E.g. in many languages null is a value of any reference type, and the type system often can't prove that a given variable, field, or return value will always be non-null. Furthermore, most type system lack means to statically describe types such as “a list with at least two elements” or “a floating-point number larger than or equal to zero”.

Such requirements should be described as far as possible using static typing; anything else has to be worked around via run-time validation. This validation is still the responsibility of any conceptual unit that wants to remain consistent.

Relaxing validation in trusted environments

In some cases, a component is only expected to be used from another trusted component (in C++ parlance: a friend). Then, defensive programming suggests that each conceptual unit should still be fully encapsulated. In practice, encapsulation is often relaxed for individual components and validation is skipped for “trused” data. If done so completely, this means that you only validate at the borders of your system. That system can still be encapsulated if and only if these privileged conceptual units are only reachable via a validation layer. In mainstream OOP languages this is done by making anything that doesn't have validation private or package-private, depending on where the validation border is.

Encapsulation vs. your scenario

Now we have learned a bit about maintaining consistency. How can we apply it to the scenario you outlined in your question? The basis is some object with two fields StartDate and EndDate with the constraint StartDate <= EndDate.

  • Shoot, I forgot StartDate/EndDate could be equal too and my code errors on half the data it tries to load from the database

    This is great!

    1. You were enforcing encapsulation.
    2. You tested your code, which caught a mistake in your validation code. Thank God you tested it before using the code on a production database!
  • Ok so apparently the StartDate has to be set before the EndDate or my EndDate validation fails. Seems odd that I can't set properties like these in any order but ok.

    This sounds like a problem with your validation code and not with validation in general. How could this problem be fixed? If StartDate == null is an illegal state, don't allow the object to get into that state. Think about using the builder pattern, proper validation in the constructor, and proper validation in the StartDate setter to enforce it's non-null. You could also disallow changing StartDate or EndDate once the object has been created.

  • But wait, unless I force the user to use a constructor with 10 parameters I can't absolutely ensure that there won't be any uninitialized properties because my validation logic will never get hit if they don't call the properties in the first place!

    • If your constructor has too many parameters, your object may be violating the Single Responsibility Principle. Otherwise, look into the builder pattern.
    • The constructor should always return an instance which is in a valid state. There is no excuse for doing otherwise.
  • Poop, I tried forcing the consumer to use ridiculously verbose constructors but now I'm having serialization headaches. The DataContractSerializer that WCF uses totally ignores my constructor. I'm sure there's a way around this though, I'll figure it out.

    I am not familiar with the DataContractSerializer, but serializers generally bypass encapsulation. A solution is not serializing the object itself, but instead a dumb record that represents permanent state (memento pattern).

  • Uh oh, bad data has been imported into the database without going through my perfectly crafted domain model. Now my domain model prevents me from displaying this invalid data to the user so I have to correct it all myself. It would be great if I could jut allow them to correct it, but my domain model doesn't know how to do anything but crash when it reads invalid data.

    The database is responsible itself for maintaining its data consistency (where “consistency” is meant in a slightly broader sense than in ACID). I previously talked about relaxing validation in a trusted context. It seems that here validation was relaxed without making sure that the database could only be reached through a validation layer, and therefore an unencapsulated system was created. The invalid data should have never been stored in the DB, and the user should have never been able to enter invalid data in the first place.

    Anyway, isn't it great that you run tests with invalid data?

5
  • 1
    One thing I want to emphasize is that when your data is consistent and written to the database, it doesn't really have to go through validation. Same goes for the serializers. Use a simple object to represent only the data (like the mentioned memento pattern) and make it possible to give it to the domain object directly (internally only of course..) Commented Oct 7, 2014 at 8:52
  • All great points, but in the end you sidestepped what is, to me, the most important part. Assume you're writing an application that will use data that doesn't always originate from your application and you can't enforce your validation rules before it gets into the database. Now your validation rules PREVENT you from having any functionality that will allow the user to correct their data through your domain. But if my UI and brokers validate instead, then it allows them to pull the records up in my system but won't let them save until they fix the errors.
    – BVernon
    Commented Oct 7, 2014 at 15:17
  • In regard to serialization, it sounds like there's just a balance between the benefits of pure OOP and the limitations of the tools you're working with.
    – BVernon
    Commented Oct 7, 2014 at 15:20
  • 1
    @BVernon No, validation on the individual object level is still crucial to maintain the consistency of your domain model. If your DB contains bad data, then yes, you must find a way to detect such data and execute an appropriate response. (…)
    – amon
    Commented Oct 7, 2014 at 16:16
  • 3
    (…) A strategy: Have your DB interface and serialization only use dumb records. When the application tries to convert records into consistent domain objects, errors may occur – which you catch. Collect the invalid records and request the user to fix them; meanwhile carry on with the valid objects. This is effectively a validation border around your domain model. This ends up partially decoupling data (the dumb records) from their behaviour (contained in domain objects). If this really is what you need, go for it, but don't just close your eyes and imagine the data is OK if it clearly isn't.
    – amon
    Commented Oct 7, 2014 at 16:16
4

You have some very good comprehensive answers. I'll just address your specific use case.

If you have two or more attributes that depend on each other then don't have individual setters and validators. As your question shows, you can get into all edge cases where one attribute is changed but not the other.

Instead, make a value object called ScheduledTimes, feed start and stop into it's constructor, validate within the constructor and then add it to your scheduled item. If the times need to be changed then replace it with a new value object.

2

OOP

When in doubt I follow 2 simple rules which help me make the right decision

  1. Encapsulation and Information hiding
  2. Low coupling and High cohesion

So this basically means Data + Behavior which you already know. But it also means that it is the class's responsibility to maintain the state of its instances, that is when you instantiate an object you should get a valid object with a valid state. These principles can be easily applied to modules and layers as well.

Validation

I can think of 2 different types of validation:

  1. Data validation (aka Input validation)
  2. Business Rules validation (your case)

As their names imply, unlike the former the latter should belong in the Domain Model containing all the business logic. Even if you use Factories and Builders to build your objects and carry out some validation there you still do it in the Domain Model. I liked the Input Validation vs. Business Rules Validation article on the subject.

Back to your question. Can [Business Rules] validation be considered as an object behavior? I would say it can but I tend to think of it as an act of maintaining an object's consistency. It's somewhat different from messages (Behavior) you send to objects in that validation is mostly hidden from the outer world and can't be triggered directly.

P.S. One way to solve your problem with "invalid" data is to have different models (CQRS) for read and write with the read model having looser requirements than that of the write model.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.