While some developers seem to prefer to implement their user data validation rules directly in the model layer (or even worse, the ORM entity layer), this very often leads to problems as described by Stefan Priebsch in his blog post on How to Validate Data.
In addition to the many issues he mentioned, you should consider the following advantages of using a separate layer to validate user input data:
- While a certain minimum level of validation must be performed in the model and persistence layer for security reasons, many validation rules are high level and depend on the use case. For example, an admin user might have less strict validation rules than a regular user. In this case, the model would require the currently active user as a dependency to perform the validation, which adds unnecessary complexity. Even worse, the very same data might be valid or invalid, depending on whether it was loaded from the database (and stored there by the admin user) or if it is coming from the frontend and should be stored in the database by a regular user. I’ve seen code like this and it’s not fun to work with.
- There are cases in which data validation rules for individual fields depend on each other, for example: If the email address is empty, you need to provide a phone number. If you’re working with exceptions and do the validation in individual setters, this doesn’t work well or at all.
- Sometimes, you want to store invalid data with a dirty flag and fix the validation issues later. This might be the case, if you have to transfer values from a paper form and then call the customer later to complete missing fields.
- More complex applications implement use-cases which store data in a number of different models at the same time. If validation is supposed to happen in the model layer, how do you decide which model is responsible?
- Sharing validation rules between backend and frontend is much easier, if they can be serialized. This is not the case for explicit code in the model layer. Using plain arrays to describe validation rules and a separate form class that interprets them (at least partly), enables reuse in backend and frontend. True, that form class must be available in all applicable programming languages, but this can be solved and usually is much less work, than code duplication as the alternative solution.
- There always comes the day, when you need to refactor or replace your model layer, for example when switching from database storage to a REST service. If your use-case related validation rules are not mixed with your model layer code, this will be much easier.
For semantic reasons, I call this additional validation layer the forms layer (see flow chart) in my own projects, but it really doesn’t matter. Just call it as you like, as long as you are able to leverage the advantage of using a separate validation layer. It helps a lot.
You’re welcome to check out my code on GitHub to see some practical examples: