RESTful API using scalaz (3)
-
Upload
yeshwanth-kumar -
Category
Software
-
view
1.012 -
download
0
Transcript of RESTful API using scalaz (3)
![Page 1: RESTful API using scalaz (3)](https://reader034.fdocuments.net/reader034/viewer/2022042611/58ecb1401a28ab4d5e8b4625/html5/thumbnails/1.jpg)
Building a RESTful API with Scalaz
Yeshwanth KumarPlatform EngineerMegam Systems
1
![Page 2: RESTful API using scalaz (3)](https://reader034.fdocuments.net/reader034/viewer/2022042611/58ecb1401a28ab4d5e8b4625/html5/thumbnails/2.jpg)
Agenda
1. Quick intro to scalaz library
2. REST API - Gateway architecture
3. Real time usecase of scalaz
![Page 3: RESTful API using scalaz (3)](https://reader034.fdocuments.net/reader034/viewer/2022042611/58ecb1401a28ab4d5e8b4625/html5/thumbnails/3.jpg)
3
Why functional ?
● complex software - well structured
● immutable - no assignment statements
● no side effects - order of execution is irrelevant
● pure functions
● concise code
● increases readability and productivity
● fun
![Page 4: RESTful API using scalaz (3)](https://reader034.fdocuments.net/reader034/viewer/2022042611/58ecb1401a28ab4d5e8b4625/html5/thumbnails/4.jpg)
4
Some(FP) with scala
● map
● flatmap
def Mee(x: Int) = List(1+x)val ListMe = List(1,2) -> ListMe: List[Int] = List(1,2)ListMe.map(x => Mee(x)) -> ListMe: List[List[Int]] = List(List(2),List(3))
ListMe.flatMap(x => Mee(x)) -> List[Int] = List(1,2)
● First class functions
● Immutable collections library
● Supports pattern matching
![Page 5: RESTful API using scalaz (3)](https://reader034.fdocuments.net/reader034/viewer/2022042611/58ecb1401a28ab4d5e8b4625/html5/thumbnails/5.jpg)
Quick intro to scalaz
● A library to write functional code in scala
● It is not hard
● Does not require super human powers
![Page 6: RESTful API using scalaz (3)](https://reader034.fdocuments.net/reader034/viewer/2022042611/58ecb1401a28ab4d5e8b4625/html5/thumbnails/6.jpg)
6
scalaz - typeclasses
1. Equality:
scala> “Paul” == 1Boolean = false
In scalaz..scala>import scala._
scala>“john” === 2Compilation Error
● Adding type-safety becomes a lot easy
2. Order typeclass:
scala> 3 ?|? 2 res0: scalaz.Ordering = GT
scala> 22 ?|? “hello”<console>:17: error: type mismatch; found : String("hello") required: Int 3 ?|? "hello"
3. Show typeclass:
scala> “vader”.showres4: scalaz.Cord = "vader"
new Thread().show
implicit val showThread =Show.shows[Thread]{_.getName}
new.Thread().show//return the name
Type class A → defines some behaviour in the form of operations →supported by Type T
then Type T → member → typeclass A
![Page 7: RESTful API using scalaz (3)](https://reader034.fdocuments.net/reader034/viewer/2022042611/58ecb1401a28ab4d5e8b4625/html5/thumbnails/7.jpg)
7
Lens - the combination of Getter & setter
case class Playlist(artist: String, ranking: Int)
val Paul = Playlist(“paul”, 9)
val Ringo = Playlist(“Ringo”, 3)
//now creating a lens
val rate = Lens.lensu[Playlist, int]((a, value) => a.copy(ranking =
value), a => a.ranking) //lens for changing the rate of the artist
val incrementSet = rate set (“paul”, 10) //setter, but not exactly
val increment = rate %= {_+1}
![Page 8: RESTful API using scalaz (3)](https://reader034.fdocuments.net/reader034/viewer/2022042611/58ecb1401a28ab4d5e8b4625/html5/thumbnails/8.jpg)
8
Lens compositions
def addressL: Lens[Person, Address] = …
def streetL: Lens[Address, String] = …
val personStreetL: Lens[Person, String] = streetL compose addressL
//getter
val str: String = personStreetL get person
//setter
val newP: Person = personStreetL set (person, "Bob St")
● bidirectional transformations between pairs of connected structures.
● neat way of updating deeply nested data structure
![Page 9: RESTful API using scalaz (3)](https://reader034.fdocuments.net/reader034/viewer/2022042611/58ecb1401a28ab4d5e8b4625/html5/thumbnails/9.jpg)
9
Validation - fail fast
● Left
● Right
object FunTimes extends Essentials {
def Need(s: Things): Validation[String, String]
{
//a for comprehension
for {
step1 <- checkFood(s)
step2 <- checkBooze(s)
step3 <- checkGasoline(s)
} yield { “Alrighty! all set!” }
}
![Page 10: RESTful API using scalaz (3)](https://reader034.fdocuments.net/reader034/viewer/2022042611/58ecb1401a28ab4d5e8b4625/html5/thumbnails/10.jpg)
10
ValidationNel
● applicative builder |@|● NonEmptyList - singly linked list to aggregate all errors● toValidationNel - a helper method
def Need(s: Things) = {
(packFood.toValidationNel |@| packBooze.toValidationNel )
..
//will return NonEmptyList(no food, no liquor)
![Page 11: RESTful API using scalaz (3)](https://reader034.fdocuments.net/reader034/viewer/2022042611/58ecb1401a28ab4d5e8b4625/html5/thumbnails/11.jpg)
Overview of the architecture
1111
Request
Ru
by A
PI
Response
Auth
HMAC
API Gateway server
FunnelResponse
FunnelRequest
Riak
Snowflake ID
![Page 12: RESTful API using scalaz (3)](https://reader034.fdocuments.net/reader034/viewer/2022042611/58ecb1401a28ab4d5e8b4625/html5/thumbnails/12.jpg)
How it all started….
package controller
import play.api.mvc._
object Logs extends Controller {
def list = Action {
Ok(views.html.index("Your new application is
ready."))
}
![Page 13: RESTful API using scalaz (3)](https://reader034.fdocuments.net/reader034/viewer/2022042611/58ecb1401a28ab4d5e8b4625/html5/thumbnails/13.jpg)
13
object Application extends Controller with LoginLogout with AuthConfigImpl {}
Authentication
object Application extends Controller with HMACAccessElement with Auth with AuthConfigImpl {}
Started abusing traits..
play-2 uses Stackable controller
![Page 14: RESTful API using scalaz (3)](https://reader034.fdocuments.net/reader034/viewer/2022042611/58ecb1401a28ab4d5e8b4625/html5/thumbnails/14.jpg)
Authentication
def post = StackAction(parse.tolerantText) { implicit request =>
val input = (request.body).toString()
val result = models.Accounts.create(input)
result match {
case Success(succ)
case Failure(err)
}
● Use a StackAction in your controller
● It first calls StackAction and composes with other actions
![Page 15: RESTful API using scalaz (3)](https://reader034.fdocuments.net/reader034/viewer/2022042611/58ecb1401a28ab4d5e8b4625/html5/thumbnails/15.jpg)
15
def Authenticated[A](req: FunnelRequestBuilder[A]): ValidationNel[Throwable,
Option[String]] = {
Logger.debug(("%-20s -->[%s]").format("SecurityActions", "Authenticated:
Entry"))
req.funneled match {
case Success(succ) => {
Logger.debug(("%-20s -->[%s]").format("FUNNLEDREQ-S", succ.toString))
(succ map (x => bazookaAtDataSource(x))).getOrElse(
Validation.failure[Throwable, Option[String]]
(CannotAuthenticateError("""Invalid content in header. API server couldn't
parse it""",
"Request can't be funneled.")).toValidationNel) }
Authentication
![Page 16: RESTful API using scalaz (3)](https://reader034.fdocuments.net/reader034/viewer/2022042611/58ecb1401a28ab4d5e8b4625/html5/thumbnails/16.jpg)
16
Validation - Usecase
● Try Catch is cumbersome
● Handle exceptions as values
def create(input: String): ValidationNel[Throwable, Option[AccountResult]]
![Page 17: RESTful API using scalaz (3)](https://reader034.fdocuments.net/reader034/viewer/2022042611/58ecb1401a28ab4d5e8b4625/html5/thumbnails/17.jpg)
17
ValidationNel def create(email: String, input: String): ValidationNel[Throwable, Option[EventsResult]] = {
(mkGunnySack(email, input) leftMap { err: NonEmptyList[Throwable] =>
new ServiceUnavailableError(input, (err.list.map(m => m.getMessage)).mkString("\n"))
}).toValidationNel.flatMap { gs: Option[GunnySack] =>
(riak.store(gs.get) leftMap { t: NonEmptyList[Throwable] => t }).
flatMap { maybeGS: Option[GunnySack] =>
maybeGS match {
case Some(thatGS) => (parse(thatGS.value).extract[EventsResult].some).successNel[Throwable]
case None => {
play.api.Logger.warn(("%-20s -->[%s]").format("Events created. success", "Scaliak returned =>
None. Thats OK."))
(parse(gs.get.value).extract[EventsResult].some).successNel[Throwable]; }}}}}
}
![Page 18: RESTful API using scalaz (3)](https://reader034.fdocuments.net/reader034/viewer/2022042611/58ecb1401a28ab4d5e8b4625/html5/thumbnails/18.jpg)
18
for-comprehension interaction with ValidationNels
private def mkGunnySack(email: String, input: String): ValidationNel[Throwable, Option[GunnySack]] = {
play.api.Logger.debug(("%-20s -->[%s]").format("models.tosca.Events", "mkGunnySack:Entry"))
play.api.Logger.debug(("%-20s -->[%s]").format("email", email))
play.api.Logger.debug(("%-20s -->[%s]").format("json", input))
val eventsInput: ValidationNel[Throwable, EventsInput] = (Validation.fromTryCatchThrowable[EventsInput,Throwable] {
parse(input).extract[EventsInput]
} leftMap { t: Throwable => new MalformedBodyError(input, t.getMessage) }).toValidationNel //capture failure
for {
event <- eventsInput
//aor <- (models.Accounts.findByEmail(email) leftMap { t: NonEmptyList[Throwable] => t })
uir <- (UID(MConfig.snowflakeHost, MConfig.snowflakePort, "evt").get leftMap { ut: NonEmptyList[Throwable] => ut })
} yield {
//val bvalue = Set(aor.get.id)
val bvalue = Set(event.a_id)
val json = new EventsResult(uir.get._1 + uir.get._2, event.a_id, event.a_name, event.command, event.launch_type, Time.
now.toString).toJson(false) //validated schema
new GunnySack(uir.get._1 + uir.get._2, json, RiakConstants.CTYPE_TEXT_UTF8, None,
Map(metadataKey -> metadataVal), Map((bindex, bvalue))).some
}}
![Page 19: RESTful API using scalaz (3)](https://reader034.fdocuments.net/reader034/viewer/2022042611/58ecb1401a28ab4d5e8b4625/html5/thumbnails/19.jpg)
JSON Serialization
case class EventsResult(id: String, a_id: String, a_name: String, command: String, launch_type: String,
created_at: String) {
def toJson(prettyPrint: Boolean = false): String = if (prettyPrint) {..}
import net.liftweb.json.scalaz.JsonScalaz.toJSON
import models.json.tosca.EventsResultSerialization
● Easy serialization
● Validated schema, hence no junk gets into your NoSQL
![Page 20: RESTful API using scalaz (3)](https://reader034.fdocuments.net/reader034/viewer/2022042611/58ecb1401a28ab4d5e8b4625/html5/thumbnails/20.jpg)
20
class EventsResultSerialization(charset: Charset = UTF8Charset) extends SerializationBase[EventsResult]
{ protected val IdKey = "id" ..}
override implicit val writer = new JSONW[EventsResult] {
override def write(h: EventsResult): JValue = {
JObject(
JField(IdKey, toJSON(h.id)) ::
JField(AssemblyIdKey, toJSON(h.a_id)) ::
Nil) }}
override implicit val reader = new JSONR[EventsResult] {
override def read(json: JValue): Result[EventsResult] = {
val idField = field[String](IdKey)(json)
val assemblyIdField = field[String](AssemblyIdKey)(json)
(idField |@|assemblyIdField |@|assemblyNameField |@|commandField |@| launchTypeField |@|
createdAtField) {
(id: String, a_id: String, a_name: String, command: String, launch_type: String, created_at:
String) =>
new EventsResult(id, a_id, a_name, command, launch_type, created_at) }}}
![Page 21: RESTful API using scalaz (3)](https://reader034.fdocuments.net/reader034/viewer/2022042611/58ecb1401a28ab4d5e8b4625/html5/thumbnails/21.jpg)
21
either[T,S] - the weird \/
def getContent(url: String): Either[String, String] =
if (url == "google")
Left("Requested URL is blocked for the good of the people!")
else
Right("Nopey!))
● Either in scala
● Similar in scalaz, called \/
isLeft, isRight, swap, getOrElse
![Page 22: RESTful API using scalaz (3)](https://reader034.fdocuments.net/reader034/viewer/2022042611/58ecb1401a28ab4d5e8b4625/html5/thumbnails/22.jpg)
22
(for {
resp <- eitherT[IO, NonEmptyList[Throwable], Option[AccountResult]] { //disjunction Throwable \/ Option with a
Function IO.
(Accounts.findByEmail(freq.maybeEmail.get).disjunction).pure[IO]
}
found <- eitherT[IO, NonEmptyList[Throwable], Option[String]] { / val fres = resp.get
val calculatedHMAC = GoofyCrypto.calculateHMAC(fres.api_key, freq.mkSign)
if (calculatedHMAC === freq.clientAPIHmac.get) {
(("""Authorization successful for 'email:' HMAC matches:
|%-10s -> %s
|%-10s -> %s
|%-10s -> %s""".format("email", fres.email, "api_key", fres.api_key, "authority", fres.authority).
stripMargin)
.some).right[NonEmptyList[Throwable]].pure[IO]
} else {
(nels((CannotAuthenticateError("""Authorization failure for 'email:' HMAC doesn't match: '%s'.""" .format
(fres.email).stripMargin, "", UNAUTHORIZED))): NonEmptyList[Throwable]).left[Option[String]].pure[IO]}}} yield
found).run.map(_.validation).unsafePerformIO()}}
![Page 23: RESTful API using scalaz (3)](https://reader034.fdocuments.net/reader034/viewer/2022042611/58ecb1401a28ab4d5e8b4625/html5/thumbnails/23.jpg)
Question?
Yeshwanth KumarPlatform EngineerMegam Systems(www.megam.io)
Twitter: @morpheyeshEmail: [email protected]
Docs: docs.megam.ioDevcenter: devcenter.megam.io