How do you model business rules validations? Although there seems to be a variety of approaches out there, some common abstractions are often present. Given an arbitrary data structure representing an entity in your domain, a validation algorithm will usually be composed by:
- A group of predicates to check business rules against the entity.
- A validation result which will either sign success or failure.
- A collection of validation errors in case of failure.
Throwing Runtime Errors
Error objects sometimes combined with
.catch() calls in promises or just surrounded by
This approach however has two fundamental issues:
Validation errors are not necessarily exceptional. They are rather expected business scenarios. They are not in the same category as network/integration failures or invalid state caused by defects, for instance.
When you throw a runtime error, you skip the current thread execution, which makes it virtually impossible to accumulate all the validation errors together. See the example below.
Notice that the first
validate() call above should have resulted in two errors instead of just one.
Monadic Error Handling
That module will give you a
Validation container that is implemented by two subtypes:
Failure. They act like
Either but customized to be explicit and handle error accumulation. See the example below (from the docs).
data.validation gives you (a) abstractions to represent success and failure, (b) the ability to accumulate errors, and (c) provides you with an interface to manipulate the results in a functional way.
The one inconvenience though is the fact that error accumulation requires a workaround. Take a look at the
validate() function above: we need to call
Success with two levels of function nesting because we are running two validation predicates. The explanation from the docs:
To aggregate the failures, we start with a Success case containing a curried function of arity N (where N is the number of validations), and we just use an
.ap-ply chain to get either the value our Success function ultimately returns, or the aggregated failures.
That limits our ability to define a dynamic validation algorithm that would work with an arbitrary number of predicates. There is though a solution proposed in the article mentioned earlier. But I believe we can bridge that better by using one of my favorite JS libraries: Ramda.
Ramda For The Rescue
The first thing we would like to do now is to be able to dynamically define an auto-curried version of a N-ary function (N being the number of predicates) and then pass it to
Success. We can accomplish that with Ramda’s
That’s the first step. It doesn’t give us much though.
curryN() is defining an auto-curried version of the
(x, y) => password function, so we don’t need nesting anymore. But we are still not dynamic, since the number of predicates is hard-coded. In order to fix that, we need an intermediate step: pass the validations being applied (in the
.ap() calls) as an argument.
Now, we are at least generic about the validations being applied to the initial
Success state. For that we used Ramda’s
reduce. The next step is to eliminate the hardcoded values in the
The trick this time was to use the length of the
validations array in order to determine how many curry levels we need. We also used Ramda’s
always to create a function that regardless of the arguments passed will always return
validate() function is completely agnostic about the arbitrary validations. It became a generic validator which will work for any number of validation functions returning a
Validation subtype, so we could even rename the
password argument to just
There is still room for improvement though. Let’s revisit our predicate based validations and try to remove the duplication in there.
It’s clear that those two functions have the exact same logic except by (a) the predicate being executed and (b) the associated error message. Let’s extract this duplicated logic and try to push it to the
And that was the last step of our journey. The
validate() function is now able to determine validation results based only on a data structure representing arbitrary validations to be performed against a target object.
Success / Failure
Why is it useful to return a container as a result?
Well, for at least two reasons:
Failurehave explicit semantics about what happened and can be used to drive subsequent actions without leaving the current thread.
Those containers provide a useful interface to safely manipulate the results in a monadic way. You can, for instance, define callbacks for success and failure and fold
validate()return value accordingly.
The above exercise on defining a Folktale based
validate() utility resulted in a tiny but brave library called Predicado. Check it out and feel free to send feedback.