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

Error handling in vasat also leverages the Cats Validated object rather than just relying on throwing exceptions. This allows for aggreation of multiple errors.

An Intro into VValids

In Scala you can Have an Either[A,B]

scala
val luckDip:Either[Bike,Car] = ???
luckyDip match {
    case Right(a:ToyotaCamry) =>
    case Right(a:MazdaRx8) =>
    case Right(a) => // some other extends car
    case Left(a:HarleyDavidson) =>
    case Left(a:BMXBike) =>
    case Left(a) => // a must be a Bike
}

Unlike the example above, the majority of the time an either was used to return either a good or a bad result

Cats improved on this calling it a Validated[A,B]

A Validated[A,B], can Either be an Invalid(obj:A) or a Valid(obj:B) this is a slight improvement from the scala Either which is a Left(obj:A) or a Right(obj:B). With later versions of scala an Eithers a 'Left' is supposed to be the wrong or bad outcome. This wasn't the best terminology

For an invalid type, the generic that would make sense would be a Throwable. If we add the concept of a cats invalidNel which is a NonEmptyList we could say the Invalid response is a list of Throwables with at least 1 throwable (ie You cant have a Invalid[EmptyList])

Make the type of the non empty list a Vasat Specific Exception type 'VError' we have a VValid[A]

A VValid[A] is the same as Validated[NonEmptyList[VError],A]

that means when its valid, its whatever you want the value to be, when its invalid its 1 or more VError objects.

A Cats Validated can have a .map( ) method that means if its valid, map to a valid of the following type

eg:

scala
val v = Valid("hello").map(h => h.toUpperCase())
// Valid("HELLO")

if you want the valid object, but you want another bit of logic that might be invalid

scala
val v = Valid("hello").andThen(h =>
if(h == "hello")
    Valid(1234)
else
   InvalidNel(VError("not hello!"))
 
)
 
// VValid[Int] Either Valid(1234), or Invalid(NonEmptyList(Verror("not hello!')))

The use of VValids over just throwing exceptions allows for errors to be collected and code to possibly move on beyond the first Invalid

eg:

scala
checkName(u)
checkPassword(u)
checkEmail(u)

If all 3 name email and passowrd were wrong for a user, checkName would throw an exception and you would know name was wrong, only to re-submit to find out password was wrong, only to re-submit to find out email was wrong

With Valids and Cats:

scala
def checkName(u:User):VValid[Noop] = ???
def checkPassword(u:User):VValid[Noop] = ???
def checkEmail(u:User):VValid[Noop] = ???
 
(checkEmail(u),checkPassword(u),checkEmail(u)).mapN((a,b,c) => "OK")

mapN is a cats function that will add NonEmptyLists of invalids, so in this case if there were 3 errors you would get a result Invalid(NonEmptyList(error1,List(erro2,errro3))

A word on Noop

Sometimes the only purpose of a Validated is to give the error if there is one, a No-operation or Noop means "I dont care what the result is if it was successful".

An example would be on a save or update method.

Futures

Since most of vasat 2 is async its expected that results can be returned at some point in the future. And the Future values are for the most parts VValids

We have the shorthand type VValidF[A]

A VValidF[A] is a Future[VValid[A]] which is a Future[Validaed[InvalidNel[VError],A]]

For operations when we want the future to return, and that return to be valid we have 3 shorthands

.andThenMap

When the future completes and is valid, take the result and map it to the following

scala
val v:VValidF[Int] = ???
 
v.andThenMap(r => r.toString)
 
// Future[VValid[String]]

.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]

.andThenFuture

When the future completes and is valid, flatMap the result against another future that also returns a VValid. This is by far the most used shorthand as you may wish to load multiple things or or load, and save and when all of that is done in a correct order, return a valid

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]

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.readObject("Abc").andThenFuture{obj =>
 
    val modifyed = obj.copy(name = "blah")
    myRepo.writeObject("Abc",modifiyed)
 
}.andThenMap{_ =>
    "Success"
}.toResult

will look like

scala
{
   status:200,
   payload:"Success",
   message:"OK",
   time:xxxxxx
}

or

scala
{
    status:<verror code>
    error:{
        // json of a verror
    },
   message:<verror message>,
   time:xxxxxx
}

VValidFs and Akka Streams

A very common case is when we want to run a list of operations. For example getting a list obj Accounts and setting them to inactive. the method .mapAsync is used to construct an Akka Source / Sink and run the items asyncrounsly

scala
import vasat.helpers.CatsHelpers._
 
val items:List[Ref[Account]] = ???
 
val res = items.mapAsync{a =>
    val modifyed = a.item.copy(active = false)
 
    // map the result to be the ID of this item
    myRepo.writeObject(a.id,modifiyed).andThenMap{_ => a.id}
}
 
// res == VValidF[List[String]]

It is important to note that with this operator, if theres more than 1 invalid entry, the Invalid NonEmptryList will return all the errors