Validation is one of the most commonly used tools when it comes to rules in applications. Its preliminary goal is to protect an actor (i.e., user, service, etc.) from changing an entity into an invalid state. This can happen in different places, from the point where users enter data into the system, to the system’s integration points to other services, to the back-end when that data is stored to durable storage (e.g., a database).
In all of those scenarios, validation is a contextual process. What this means is validating an object (e.g., an order) is meaningful in the context of the action taking place. For example, let’s say you have an Order object in an application. The validity of the order depends on its context (i.e., inserting a new order, editing an existing one, assigning an order to customer’s agent to process and so on).
A validating object is required when these are crossing your application services’ boundaries. Your application may have hundreds of different domain services, each of which is designed to address a specific responsibility. As an object comes to that service boundary, it needs to be validated in order to guarantee that a particular service is processing an input in a valid state.
To write a validation engine flexible enough to be used in all your application services, there are different approaches, but some of the characteristics that need to be considered during the design phase are:
- Making the validation engine able to accept input parameters
- Allowing the validation rules be modelled and executed outside of your codebase
- Allowing validation rules to be divided into different logic that can be reused
- Allowing those small pieces of validation rules (i.e., logic) to call each other
- Make it possible to unit test the validation rules for a specific scenario and for small individual logic
What is required?
To make these happen, the validation rules must:
- Accept parameters (input and output)
- Call each other (and pass values and return results)
In order to do this, they should be modelled structurally as shown below:
The “Green” rectangle is your actual validation rule that has two pieces of internal logic: “Blue” and “Purple”. Now your application can call to either of them. The parent main validation rule (Green), or the children (Blue, Purple), directly and indirectly (via parent). The parent rule manages the main flow of the validation and the child logic is the reusable component of the validation. They can be shared within the parent (Green) or between multiple validation rules. And as it is shown, all of them (Green, Blue and Purple) can accept input and output parameters. The local parameter in the picture is for a more advanced scenario when we want to implement some sharing between logic (Blue and Purple).
Each logic now contains Boolean operations and Expression evaluation mechanisms that allows complex logic to be implemented. This also allows combining the value after evaluation using those Boolean operators. In some advanced scenarios, you can allow logic to even call objects and types methods or properties and combine values to evaluate the results. Then that result can participate in the Logic Boolean operation.
How and where can these be used?
If the validation engine you are designing does not have some of these abilities, sooner or later you see yourself hacking either your actual application codebase, validation engine or rules to get a validation task working as required.
In a Domain-Driven Design (DDD) approach, if you separate the application into services and models, then services must fulfil the single responsibility of accomplishing a task on part of the main model. In this architectural approach we have different design decisions to take:
- Every service has its own model to process, or a specific part of a bigger model
- A model (or specific part of the bigger model) can be processed with different services for different purposes
In either of these approaches if the above mentioned guideline for validation is followed, your validation engines will be able to deal with the complexity.
Modelling the rules and logic are important as well. Models are light. In a textual form that engine can read them and create rule objects based on the model’s source. For example, this modelling helps you to simply execute rules on both the client and server side as simply as possible. As the models are stored as a textual format, they can be transferred simply to either side and the engine can create rules using these. This means, there would not be any need for serializing rules objects and passing them across the wire to execute them on the client. Also, you can maintain different versions of models using any tools you like, because they are just a simple text (e.g., XML).
Benefits of this design
- Scale very well and validation execution can be parallelized
- You can execute rules on both the client and servers easily
- Increases reusability of logic and validation rules
- Extending and injecting new behaviours into validation rules are easy (when evaluation is implemented)
- Versioning rules are easy (they are modelled in human readable text format)
- Transferring and executing rules to any tiers of your application is cheap
Last updated May 2nd, 2017 at 06:54 pm, Published November 1st, 2013 at 06:54 pm