Appearance
Asyncronus Design
Vasat is designed to take full advantage of asyncronus patterns.
The majority of the methods used in Vasat return a scala Future
That wraps a Cats Validated
this allows for aggregation of multiple errors through a call.
A VValid
Definition
A VValid[A]
is the same as Validated[NonEmptyList[VError],A]
When its valid, the return is the type A
, when its invalid its 1 or more VError objects.
For details on how Validated objects work, check the Cats Validated Documentation.
A 'Valid' VValid
You can cast any object to a VValid using the cats implicit function
scala
import cats.implicits._
"any old object".valid
//VValid[String]
An 'Invalid' VValid
You can make an Invalid state with taking a vasat error VError
and again using the cats implicits
scala
import cats.implicits._
VError(500,"Processing failed").invalidNel
Important
If a single error is being throw, it must be an .invalidNel
. If there is a list of errors. They must be first turned into a NonEmptyList and can use the Cats implicit .invalid
Aggregating Errors
The Cats library designed Validated objects to be able to aggregate.
scala
import cats.implicits._
val v1:VValid[String] = ???
val v2:VValid[Int] = ???
(v1,v2).mapN( (validV1, validV2) =>
// both v1 and v2 are valid
validVi + " " + validV2
)
Is important to note that when there is any invalid in this scenario they can aggregate.
scala
val v1:VValid[String] = ???
v1 match {
case Valid(s:String) => // when valid
case Invalid(e) =>
e.head // first error (there must be at least one error since this is a NEL)
e.toList // there can also be more than 1
}
The Vasat CatsHelpers
adds another method to help merge errors on an array of VValids that are all the same type
scala
import vasat.helpers.CatsHelper._
val vList = List[VValid[String]] = ???
vList.flatMapN(res =>
// res is a List[String] and this only runs if all the list is valid
)
A VValidF
Most of vasat is async. It expected that results can be returned at some point in the future.
Definition
A VValidF[A]
is the same as Future[VValid[A]]
which is in long form Future[Validated[NonEmptyList[VError],A]]
For operations when we want the future to return, and that return to be valid Vasat has several convenience methods
.andThenMap
When the future completes and is valid, take the result and map it to the following object.
scala
val v:VValidF[Int] = ???
v.andThenMap(r => r.toString)
// VValidF[String]
This function is to be used when theres no reason to need to perform another check that might result in an error.
.andThenVValid
When the future completes and is valid, take the result and the equate another VValid expression
scala
val v:VValidF[String] = ???
v.andThenVValid(r =>
if(h == "hello")
Valid(1234)
else
InvalidNel(VError("not hello!"))
)
)
// Future[VValid[Int]] aka VValidF[Int]
scala
import cats.implicits._
val v:VValidF[String] = ???
v.andThenVValid(r =>
if(h == "hello")
1234.valid
else
VError("not hello!").invalidNel
)
)
// Future[VValid[Int]] aka VValidF[Int]
This is best used when the function can determine a synchronous VValid expression.
.andThenFuture
When the future completes and is valid, flatMap the result against another future that also returns a VValidF.
scala
val v:VValidF[String] = ???
v.andThenFuture(r =>
val v2:VValidF[Long] = ???
v2
)
// VValidF[Long]]
An example
scala
val res = myRepo.readObject("Abc").andThenFuture{obj =>
val modifyed = obj.copy(name = "blah")
myRepo.writeObject("Abc",modifiyed)
}.andThenMap{_ =>
"Success"
}
// res == VValidF[String]
This is by far the most used shorthand as you may wish to load multiple things such as load and save. The errors can cascade and aggregate.
.recoverFromVError
Important
A Failed VValidF
is NOT a Failed
Future
This works similar to a Futures .recover
or .recoverWith
.
Consider the following:
scala
val v:VValidF[Any] = ???
v.andThen{
case Success(Valid(s)) => // successful
case Success(Invalid(s)) => // failed due to a VError (soft fail)
case Failure(t) => // failed due to an uncaught exception (hard fail)
}
Using .recoverFromVError
will recover from the 'soft' fail. A soft fail will not trigger the Future's .recover
or .recoverWith
since the future has not failed.
The following example recovers from a VError and a hard fail cause by the Future not running for other reasons:
scala
val v:VValidF[Any] = ???
v.recoverFromVError{
case e => "i just softFailed".validF
}.recoverWith{
case t:Throwable => Future.successful("i just hard failed")
}
VValidFs and play
A VValid and VValidF have a json serializer what will render the result as the familiar vasat json payload response. There are some play helpers to make this happen
scala
import vasat.play.helpers.PlayHelpers._
val res = myRepo.writeObject("Abc",???).andThenMap{_ =>
"Success"
}.toResult
will look like
js
{
status:200,
payload:"Success",
message:"OK",
time:xxxxxx
}
or
js
{
status:'<verror code>',
error:{
// json of a verror
},
message:'verror message',
time:xxxxxx
}
VValidFs and Akka Streams
mapAsync
A very common case is when we want to run a list of VValidF's. For example getting a list of Accounts and updateing each one. The method .mapAsync
is used to construct an Akka Source / Sink and run the items asynchronously. This method will wait for each future to complete execution before starting the next.
scala
import vasat.helpers.CatsHelpers._
val items:List[Ref[Account]] = ???
val res = items.mapAsync{a =>
val modified = a.item.copy(active = false)
// After save, map the result to be the ID of this item
myRepo.writeObject(a.id,modified).andThenMap{_ => a.id}
}
res.andThenMap{ids =>
println("All records saved OK")
ids // list of each object's id
}
// res == VValidF[List[String]]
Just like the Aggregating errors above. If there is one of more Invalids they will aggregate into the one Invalid[NonEmptyList[VError]]
mapParallel
This works exactly like mapAsyc except instead of waiting for the futures to execute one at a time. You can specify a level of concurrency.
scala
import vasat.helpers.CatsHelpers._
val items:List[Ref[Account]] = ???
val res = items.mapParallel(5){a =>
val modified = a.item.copy(active = false)
// map the result to be the ID of this item
myRepo.writeObject(a.id,modified).andThenMap{_ => a.id}
}
// res == VValidF[List[String]]
Example above will do 5 myRepo.writeObject
operations in parallel. The mapAsync example above would do only 1 at a atime.