Validation in Scala: Fail Fast and Fail Slow
Introduction
We all know that validation is one of important thing in Software development, it’s not only help us make sure our logic is correct but also protect our system from unexpected exception.
In this post, I’m going to show you two kind of validation in Scala by some simple example.
It’s an implementation not a concept so I hope that it’s not too sleepy for reader.
Let’s begin.
Validation in Scala
In general, sometime we have two use cases of validation, the first case is return one error as soon as possible.
I’ll call it as Fail Fast because our program will stop when it meets the first failure.
And the second one is try to get as many error as possible. Maybe the name Fail Slow is suitable for this case, the reason is we looking up for many failure until our validator finish it’s work.
So let’s look at bellow example:
I am considering to buy a camera, so I need to check it's brand new or 2nd, it has full accessories or not, it's price is higher or lower my budget.
I have case class Camera like this:
case class Camera( isUsed: Boolean,
hasFullAccessories: Boolean,
expectedPrice: Double)
Fail Fast
I’m going to use type Try for my first checking function.
Try[Throwable, T] is good type for validation because it’s wrapped type with the left side is alway an Exception and the right side is Success(T) value.
def checkCamera(camera: Camera): Unit = {
val result = for {
newCamera <- checkUsedCondition(camera)
fullCamera <- checkFullAccessories(camera)
goodCamera <- checkSuitablePrice(camera)
} yield goodCamera
result match {
case Success(camera) => // Okay I will buy it
case Failure(issue) => // I'm considering
}
}
It’s simple and easy to understand that if I don’t get any issue from this camera, I will buy it.
If this camera is 2nd but the price is acceptable, I’m still considering.
The Monad or the For comprehension above validated by running each function inside itself then return immediately the failure value (an exception).
The implementation of these function as bellow:
Each validator will return an Exception or a Camera type
def checkUsed(camera: Camera): Try[Camera] =
if (camera.isUsed)
throw new Exception("Camera was used")
else camera
def checkFullAccessories(camera: Camera): Try[Camera] =
if (!camera.hasFullAccessories)
throw new Exception("Camera didn't have full accessories")
else camera
def checkSuitablePrice(camera: Camera): Try[Camera] = {
val budget = 1000 //USD
if (budget < camera.expectedPrice)
throw new Exception("Too hight !!!")
else camera
}
But if I need all information from this camera instead of only one, it’s better to know about status of this camera.
How can I do that ?
It’s time to use the second technic: Fail Slow
Fail Slow
scalatic.{Or, Every}
I will try to use scalactic
library because it’s useful and very expressive.
Let’s see the implementation of validate functions first
import scalatic._
// ErrorMessage is a type alias for String declared in the org.scalactic package object
def checkUsedCondition(camera: Camera): Camera or One[ErrorMessage] =
if (camera.isUsed)
Bad("Camera was used")
else Good(camera)
def checkFullAccessories(camera: Camera): Camrea or One[ErrorMessage] =
if (!camera.hasFullAccessories)
Bad("Camera didn't have full accessories")
else Good(camera)
def checkSuitablePrice(camera: Camera): Camera Or One[ErrorMessage] = {
val budget = 1000 //USD
if (budget < camera.expectedPrice)
Bad("Too hight !!!")
else Good(camera)
}
It’s look similar to the Fail fast implement but the return type is diffirent.
Instead of using Try[Camera], now I have other type Camera or One[ErrorMessage].
Actually, this type desugar is One[Camera, ErrorMessage].
It’s not unbiased type, that means left side is not for failure case and right side for success failure.
The only thing we need to know here is if it’s passed validation, it’ll return Good type otherwise is Bad type.
Then our main validate function look like this
def checkCamera(camera: Camera): Unit = {
val result = for {
newCamera <- checkUsedCondition(camera)
fullCamera <- checkFullAccessories(camera)
goodCamera <- checkSuitablePrice(camera)
} yield goodCamera
result match {
case Good(camera) => //Okay I will buy it
case Bad(issue) => issue match {
case One(i) => // Only one little issue, I'm considering
case Many(i) => // I'll not buy this one
}
}
}
It’s not much diffirent than previous function but the output result is not the same.
If you are interesting at this library, you can read their User guide for more information.
scalaz.ValidationNel
Other interesting library (But hard to use and undestand) is scalaz
.
I’m trying ValidationNel type with Applicative Functor instead of Monad.
You may confuse that what is ValidationNel ?!
The Nel word is shortcut of Non Empty List.
That means, if we don’t have success value, we will have a list of error and this list’ll never be empty. (If it’s empty, we have success value)
Now let’s see it in action:
import scalaz._
def checkUsedCondition(camera: Camera): ValidationNel[String, Camera] =
if (camera.isUsed)
"Camera was used".fail
else camera.success
def checkFullAccessories(camera: Camera): ValidationNel[String, Camera] =
if (!camera.hasFullAccessories)
"Camera didn't have full accessories".fail
else camera.success
def checkSuitablePrice(camera: Camera): ValidationNel[String, Camera] = {
val budget = 1000 //USD
if (budget < camera.expectedPrice)
"Too hight !!!".fail
else camera.success
}
The return type now is ValidationNel[String, Camera] and it’s biased type (Left-Right rule)
I’ll combine these function to my final checking function:
def checkCamera(camera: Camera, expectedPrice: Double): Unit = {
val result = (checkUsedCondition(camera) |@| checkFullAccessories(camera) |@| checkSuitablePrice(camera)) {
case (_, _, _) => camera
}
result match {
case Success(camare) => // I will buy it
case Failure(issue) => // NonEmptyList(...)
}
}
Each validator now will be combined by |@|
with function which map from case(_, _, _) to a camera. It’s Applicative Functor. (I’m not going to apply new object here, but in other context you can do that)
Then the result we have is Success(Camera) or Failure(NonEmptyList).
Note that Sucess and Failure came from scalaz
, it’s not native Scala (scala.util
)
Conclusion
It’s depends on our context to use the right technic.
In my opinion there is no perfect library or framework, our choice is right tools for right jobs.
In Scala we have many ways to solve problem, some complex some simple, but we need to implement our code clean and readable as much as possible.
And if you have other way to validate logic, please feel free to suggest me, I’m hopeful to know your ideas.
Thanks for reading my long post. Hope you enjoy !
Reference
- https://github.com/scalaz/scalaz
- http://www.scalactic.org/
- http://eed3si9n.com/learning-scalaz/Applicative.html
- https://gist.github.com/oxbowlakes/970717