Skip to content

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.