Shapeless and Applicative Functors for Server side validations
(Work in progress, there are typos and inconsistencies, so excuse)
This is a quick deep dive into shapeless and Scalaz applicative builder pattern working together and solving a use-case for better understanding. You may well be tweaking further to get your stuff done based on what is presented here.
Audience
This is targeted for those who know type classes (Haskell type class taken to Scala), Generic Repr in shapeless, applicative functors, applicative builder pattern in Scalaz/Cats (and optionally tagged type)
Problem(s)
A typical example of using applicative builders is when you need to validate an entity where the validation of each field in the entity returns an applicative functor. And when you have dozens of such entities (never dealt with it until yesterday as I am writing this) sharing fields across each other, you finally have a bloated boiler plate of Advanced (for some) Scala.
Ok, so how many times have you hacked around validating fields each one returning an applicative and then write long boiler plates of using applicative builder pattern for each of your aggregates in the app? Something like this:
(column1.validate|@| column2.validate|@| column3.successNel[ValidationError]) {Entity.apply}
How many times have you re-written the validation logic for fields that are shared across aggregates, and then write applicative builders explicitly every time?
Or at the very least, how many times you were confused on where to put all of the boiler plates of applicative builders in your application?
Solution
What do you think of a solution that provides the following as an alternative?
When you provide validations (only) for every field values in your app that you use in your application:
* You get validation for all your entity objects, value objects and aggregates for FREE
* Return the list of errors or the validated entity for all your entities in your application with just one|@|
in your entire app.
* You get a compile time if you try to use a column that doesn’t have a validation instance in any entity or value objects (Well, this is not a big deal you know but sounds good though :D)
Let’s write some code
The validation behaviour is encoded as a type class, with a validate function returning an applicative functor.
Let’s use tagged types instead of primitives just to differentiate your types are your own types :D
Let’s define an aggregate (may not make much sense, doesn’t matter)
Let’s define instances for each field
Ok, now we have instances of Validation for each field in SuperExchange. Now our intention is to get an instance for SuperExchange for free.
For that, first let’s create an instance for any HList, provided we have the validation instance for its the head and tail (which in turns has a validation instance for its head and tail and it goes on recursively until HNil).
Once we have the instance for HList
(that means we have a validation instance for Generic.Repr
), and once we have a Generic instance for SuperExchange (which you get for Free from shapeless), we can derive the validation instance for SuperExchange.
Let’s have that in place:
You may note that, there is a bit going on with respect to gen.to
and gen.from
, but that is just us trying to satisfy the compiler work and it is not something far from the docs of shapeless. When you do env.validate(gen.to(b))
what you get in return is a Validation[Generic.Repr]
and not for A
(which is in fact going to be our aggregate or entity). To get the validation instance for A
you map it further on the returned applicative and get A
from Repr
using gen.from
That’s it. Now that you have the instances freely available for any entities that use (and only use) ExchangeId and Name types. Let’s test this:
Let’s work it for other entities that re-uses some of the entities, and see if we can forget about applicative builders and be in a nice world.
Please note that we have given validation instance for that extra field newNumber
, else we get a compile time error.
Conceptually it is very pleasing: “Provide validations for all the fields, and any entities that use them are automatically validated”.
Stare at these codes for a while, and I will probably come up with a better explanation of the concepts later on.
Thanks and have fun!