How to separate data validation from my simple domain objects (POCOs)?

This question is an agnostic of the language, but I'm a C # guy, so I use the term POCO to refer to an object that only stores data, usually using the getter and setter fields.

I just redesigned my domain model to be the POCO super-duper, and I left a couple of problems on how to ensure that property values ​​make sense in the domain.

For example, the EndDate of a service must not exceed the EndDate of the contract in which the Service resides. However, it seems that the SOLID violation put the check in set.EndDate setter, not to mention the fact that as the number of checks to be performed grows, my POCO classes will become cluttered.

I have some solutions (post in the answers), but they have their drawbacks, and I wonder which ones are favorite approaches to solving this dilemma?

+7
language-agnostic design solid-principles separation-of-concerns modular-design
source share
8 answers

I think you start with a bad guess, i.e. that you must have objects that do nothing but store data, and have no methods other than accessories. The whole point of having objects is to encapsulate data and behavior. If you have a thing that is just basically structured, what actions do you encapsulate?

+6
source share

I always hear people argument for the "Validate" or "IsValid" method.

Personally, I think this might work, but with most DDD projects, you usually end up with a few checks, valid depending on the specific state of the object.

Therefore, I prefer "IsValidForNewContract", "IsValidForTermination" or the like, because I believe that most projects end with several such validators / states for each class. It also means that I am not getting any interface, but I can write aggregate validators that very well reflect the business conditions that I approve.

I really believe that common solutions in this case very often divert attention from what is important - what the code does - for very little benefit in technical elegance (interface, delegate or something else). Just vote for me;)

+3
source share

My colleague came up with an idea that worked out well. We never came up with a great name, but we called it inspector / judge.

The inspector will look at the object and tell you all the rules that he violated. The judge will decide what to do with it. This separation will allow us to do a couple of things. This allowed us to establish all the rules in one place (Inspector), but we could have several judges and choose Fate according to context.

One example of the use of several judges is based on the rule that the Client must have an address. It was a standard application with three levels. At the UI level, the Judge would create something that the user interface could use to indicate the fields to be filled. UI judge made no exceptions. There was another judge on the service layer. If he finds the Client without an address during the save, he will throw an exception. At this point, you really need to stop everything that happens.

We also had judges who were more stringent as the state of the facilities changed. This was an insurance application, and during the Quoting process, the policy was allowed to remain in an unfinished state. But as soon as this policy was ready for active, much had to be set. Therefore, the candidate judge on the service side was not as strict as the executive judge. However, the rules used in the Inspector were the same, so you can still say that it was not complete, even if you decided not to do anything about it.

+3
source share

One solution is for each DataAccessObject to accept a list of Validators. When the "Save" function is called, it performs a preliminary check of each validator:

public class ServiceEndDateValidator : IValidator<Service> { public void Check(Service s) { if(s.EndDate > s.Contract.EndDate) throw new InvalidOperationException(); } } public class ServiceDao : IDao<Service> { IValidator<Service> _validators; public ServiceDao(IEnumerable<IValidator<Service>> validators) {_validators = validators;} public void Save(Service s) { foreach(var v in _validators) v.Check(service); // Go on to save } } 

The advantage is very clear SoC, the disadvantage is that we do not get the check until Save () is called.

+2
source share

In the past, I usually delegated validation to a service myself, for example, ValidationService. It basically still hears the DDD philosophy.

Internally, this will contain a collection of Validators and a very simple set of public methods, such as Validate (), that can return a collection of an error object.

Very simple, something like this in C #

 public class ValidationService<T> { private IList<IValidator> _validators; public IList<Error> Validate(T objectToValidate) { foreach(IValidator validator in _validators) { yield return validator.Validate(objectToValidate); } } } 

Validators can be added to the default constructor or introduced through some other class, for example, ValidationServiceFactory.

+2
source share

I think this will probably be the best place for logic, but it's just me. You may have some IsValid method that also checks all conditions and returns true / false, perhaps some ErrorMessages collection, but this is an iffy topic, since error messages are not really part of the domain model. I am a bit biased as I did some work with RoR and that essentially its models do.

0
source share

Another possibility is for each of my classes to implement

 public interface Validatable<T> { public event Action<T> RequiresValidation; } 

And each installer for each class will raise an event before tuning (maybe I could achieve this through attributes).

The advantage is real-time validation. But the code is more dirty, and it is unclear who should do the attachment.

0
source share

Here is another opportunity. Validation is performed through a proxy or decorator of the Domain object:

 public class ServiceValidationProxy : Service { public override DateTime EndDate { get {return EndDate;} set { if(value > Contract.EndDate) throw new InvalidOperationexception(); base.EndDate = value; } } } 

Advantage: instant validation. Easy to configure via IoC.

Disadvantage: if a proxy, the checked properties should be virtual, if the decorator of all domain models should be based on the interface. Validation classes will end up being a bit heavyweight - proxies must inherit the class, and decorators must implement all methods. Naming and organization can be confusing.

0
source share

All Articles