Slide 1

Slide 1 text

Functional Web Services Markus Hauck codecentric AG February 28, 2017

Slide 2

Slide 2 text

Intro A First DSL Web Services With Http4s Runner’s Paradise DSL Final Encodings and http4s Testing What are we gonna do tonight? Markus Hauck (codecentric AG) Functional Web Services slide 1

Slide 3

Slide 3 text

Intro A First DSL Web Services With Http4s Runner’s Paradise DSL Final Encodings and http4s Testing Markus Hauck (codecentric AG) Functional Web Services slide 2

Slide 4

Slide 4 text

Intro A First DSL Web Services With Http4s Runner’s Paradise DSL Final Encodings and http4s Testing What are we going to do tonight • write a small web service • with http4s • using an embedded DSL • emphasize functional style https://github.com/markus1189/runnersparadise Markus Hauck (codecentric AG) Functional Web Services slide 3

Slide 5

Slide 5 text

Intro A First DSL Web Services With Http4s Runner’s Paradise DSL Final Encodings and http4s Testing Embedded DSLs • embedded in general purpose language (host language) • powerful: separate description of program from execution • initial encoding: Free Monads • final encoding: typeclasses + instances • also: deep vs shallow embedding Markus Hauck (codecentric AG) Functional Web Services slide 4

Slide 6

Slide 6 text

Intro A First DSL Web Services With Http4s Runner’s Paradise DSL Final Encodings and http4s Testing A First DSL • let’s start with our first DSL • arithmetic operations: + and - (negation) • how would you do this in an initial encoding? Markus Hauck (codecentric AG) Functional Web Services slide 5

Slide 7

Slide 7 text

Intro A First DSL Web Services With Http4s Runner’s Paradise DSL Final Encodings and http4s Testing Initial Encoding sealed trait Exp case class Lit(x: Int) extends Exp case class Neg(e: Exp) extends Exp case class Add(e1: Exp, e2: Exp) extends Exp • writing a program //(8 + -(1 + 2)) Add(Lit(8), Neg(Add(Lit(1), Lit(2)))) Markus Hauck (codecentric AG) Functional Web Services slide 6

Slide 8

Slide 8 text

Intro A First DSL Web Services With Http4s Runner’s Paradise DSL Final Encodings and http4s Testing Initial Encoding DSL AST Interpreter Markus Hauck (codecentric AG) Functional Web Services slide 7

Slide 9

Slide 9 text

Intro A First DSL Web Services With Http4s Runner’s Paradise DSL Final Encodings and http4s Testing Properties of the Initial Encoding • nice to have ADT with possible cases • flexible interpretation, evaluate, pretty print, . . . • bad: extending with e.g. multiplication is not independent • let’s try something different: final encoding • still embedded DSL, get rid of AST Markus Hauck (codecentric AG) Functional Web Services slide 8

Slide 10

Slide 10 text

Intro A First DSL Web Services With Http4s Runner’s Paradise DSL Final Encodings and http4s Testing Final Encoding DSL AST Interpreter Markus Hauck (codecentric AG) Functional Web Services slide 9

Slide 11

Slide 11 text

Intro A First DSL Web Services With Http4s Runner’s Paradise DSL Final Encodings and http4s Testing Live Coding: Our First DSL Markus Hauck (codecentric AG) Functional Web Services slide 10

Slide 12

Slide 12 text

Intro A First DSL Web Services With Http4s Runner’s Paradise DSL Final Encodings and http4s Testing Review: Final Encoding sealed trait Exp[A] { def lit(x:Int): A def neg(e:A): A def add(e1 A, e2:A): A } new Exp[Int] { def lit(x:Int) = x def neg(e: Int) -e def add(e1:Int,e2:Int) = e1 + e2 } • define typeclass for syntax • define instance for semantics • typeclass also called Symantics • languages are independent, expressed via constraints on types Markus Hauck (codecentric AG) Functional Web Services slide 11

Slide 13

Slide 13 text

Intro A First DSL Web Services With Http4s Runner’s Paradise DSL Final Encodings and http4s Testing Web Services with Http4s Markus Hauck (codecentric AG) Functional Web Services slide 12

Slide 14

Slide 14 text

Intro A First DSL Web Services With Http4s Runner’s Paradise DSL Final Encodings and http4s Testing http4s What is http4s A typeful, purely functional, streaming library for HTTP clients and servers in Scala. • typeful — self-documentation and compile-time verification • purely functional — promote composability and reasoning • streaming — large payloads in constant space and websockets (currently being rewritten to use cats and fs2) Markus Hauck (codecentric AG) Functional Web Services slide 13

Slide 15

Slide 15 text

Intro A First DSL Web Services With Http4s Runner’s Paradise DSL Final Encodings and http4s Testing Writing Web Services With Http4s • define HttpService using the built-in DSL • really just Request => Task[Response] • (Task[A] is a better Future[A]) • define routes via pattern matching: HttpService { case req @ GET -> Root / "hello" => handleHelloWorld(req) } Markus Hauck (codecentric AG) Functional Web Services slide 14

Slide 16

Slide 16 text

Intro A First DSL Web Services With Http4s Runner’s Paradise DSL Final Encodings and http4s Testing Our Domain Runners Races Registrations Markus Hauck (codecentric AG) Functional Web Services slide 15

Slide 17

Slide 17 text

Intro A First DSL Web Services With Http4s Runner’s Paradise DSL Final Encodings and http4s Testing Our API GET "localhost/runner/" POST "localhost/runner" GET "localhost/race/" POST "localhost/race" GET "localhost/registration/" PUT "localhost/registration" Markus Hauck (codecentric AG) Functional Web Services slide 16

Slide 18

Slide 18 text

Intro A First DSL Web Services With Http4s Runner’s Paradise DSL Final Encodings and http4s Testing Our API HttpService { case GET -> Root / "runner" / RunnerIdVar(v) => ??? case req @ POST -> Root / "runner" => ??? case GET -> Root / "race" / RaceIdVar(v) => ??? case req @ POST -> Root / "race" => ??? case GET -> Root / "registration" / RaceIdVar(v) => ??? case req @ PUT -> Root / "registration" => ??? } Markus Hauck (codecentric AG) Functional Web Services slide 17

Slide 19

Slide 19 text

Intro A First DSL Web Services With Http4s Runner’s Paradise DSL Final Encodings and http4s Testing The Runners Paradise DSL trait RunnerAlg[F[_]] { def saveRunner(runner: Runner): F[Unit] def findRunner(id: RunnerId): F[Option[Runner]] } • we use a higher-kinded type F • kind: * -> *, i.e. it needs another type of kind * • results are wrapped in F[ ] • instances choose the concrete F Markus Hauck (codecentric AG) Functional Web Services slide 18

Slide 20

Slide 20 text

Intro A First DSL Web Services With Http4s Runner’s Paradise DSL Final Encodings and http4s Testing The Runners Paradise DSL trait RaceAlg[F[_]] { def saveRace(race: Race): F[Unit] def findRace(id: RaceId): F[Option[Race]] } trait RegistrationAlg[F[_]] { def saveReg(reg: Registration): F[Unit] def findReg(id: RaceId): F[Option[Registration]] } Markus Hauck (codecentric AG) Functional Web Services slide 19

Slide 21

Slide 21 text

Intro A First DSL Web Services With Http4s Runner’s Paradise DSL Final Encodings and http4s Testing The Runners Pradise DSL: Registrations • allow registration of runners for races • if: • race exists • runner exists • race has free slots • reality: Either[RegistrationError,Registration] Markus Hauck (codecentric AG) Functional Web Services slide 20

Slide 22

Slide 22 text

Intro A First DSL Web Services With Http4s Runner’s Paradise DSL Final Encodings and http4s Testing Fair Warning: Fancy Code Incoming Markus Hauck (codecentric AG) Functional Web Services slide 21

Slide 23

Slide 23 text

Intro A First DSL Web Services With Http4s Runner’s Paradise DSL Final Encodings and http4s Testing The Runners Paradise DSL def registerOpt[F[_]:Monad:RunnerAlg:RaceAlg:RegistrationAlg]( runnerId: RunnerId, raceId: RaceId): F[Option[Registration]] = { val M = Monad[OptionT[F, ?]] for { runner <- OptionT(RunnerAlg().findRunner(runnerId)) race <- OptionT(RaceAlg().findRace(raceId)) reg <- OptionT(RegistrationAlg().findReg(raceId)). orElse(M.point(Registration(race, Set()))) newReg <- OptionT(reg.add(runner).pure[F]) _ <- OptionT(RegistrationAlg().saveReg(newReg). map(Option(_))) } yield newReg }.run Markus Hauck (codecentric AG) Functional Web Services slide 22

Slide 24

Slide 24 text

Intro A First DSL Web Services With Http4s Runner’s Paradise DSL Final Encodings and http4s Testing Registration Program def registerOpt[F[_]: // still abstract Monad: // flatMap RunnerAlg: // runners RaceAlg: // races RegistrationAlg]( // registrations runnerId: RunnerId, // runner id raceId: RaceId // race id ): F[Option[Registration]] // wrapped in F Markus Hauck (codecentric AG) Functional Web Services slide 23

Slide 25

Slide 25 text

Intro A First DSL Web Services With Http4s Runner’s Paradise DSL Final Encodings and http4s Testing Getting More Concrete: Instances • to actually run programs, we need to have instances • need to satisfy all constraints • try to keep them minimal • pure in-memory, cassandra, postgres, redis, . . . Markus Hauck (codecentric AG) Functional Web Services slide 24

Slide 26

Slide 26 text

Intro A First DSL Web Services With Http4s Runner’s Paradise DSL Final Encodings and http4s Testing Instances for our DSL class Cass[A]( val value: ReaderT[Task, RunnersParadiseDb, A] ) extends AnyVal { def run: RunnersParadiseDb => Task[A] = value.run } • RunnersParadiseDb => Task[A] • given a connection to the DB, asynchronous results Markus Hauck (codecentric AG) Functional Web Services slide 25

Slide 27

Slide 27 text

Intro A First DSL Web Services With Http4s Runner’s Paradise DSL Final Encodings and http4s Testing Instances for our DSL class Cass[A]( val value: ReaderT[Task, RunnersParadiseDb, A] ) extends AnyVal { def run: RunnersParadiseDb => Task[A] = value.run } def saveRunner(runner: Runner): Cass[Unit] = new Cass(ReaderT(_.runners.save(runner).void)) Markus Hauck (codecentric AG) Functional Web Services slide 26

Slide 28

Slide 28 text

Intro A First DSL Web Services With Http4s Runner’s Paradise DSL Final Encodings and http4s Testing Putting It Together • remember signature of routes? Request => Task[Response] • at the end our program has to produce a Task • but we don’t want to be limited by this Markus Hauck (codecentric AG) Functional Web Services slide 27

Slide 29

Slide 29 text

Intro A First DSL Web Services With Http4s Runner’s Paradise DSL Final Encodings and http4s Testing Let’s Have A Look Markus Hauck (codecentric AG) Functional Web Services slide 28

Slide 30

Slide 30 text

Intro A First DSL Web Services With Http4s Runner’s Paradise DSL Final Encodings and http4s Testing Typeclasses Need Laws • typeclasses should always come with laws • what about RunnerAlg? trait RunnerAlg[F[_]] { def saveRunner(runner: Runner): F[Unit] def findRunner(id: RunnerId): F[Option[Runner]] def listRunner: F[Vector[Runner]] } Markus Hauck (codecentric AG) Functional Web Services slide 29

Slide 31

Slide 31 text

Intro A First DSL Web Services With Http4s Runner’s Paradise DSL Final Encodings and http4s Testing Typeclasses Need Laws • typeclasses should always come with laws • what about RunnerAlg? trait RunnerAlg[F[_]] { def saveRunner(runner: Runner): F[Unit] def findRunner(id: RunnerId): F[Option[Runner]] def listRunner: F[Vector[Runner]] } • saveRunner *> saveRunner • findRunner *> findRunner • saveRunner *> findRunner • saveRunner *> listRunners Markus Hauck (codecentric AG) Functional Web Services slide 29

Slide 32

Slide 32 text

Intro A First DSL Web Services With Http4s Runner’s Paradise DSL Final Encodings and http4s Testing ScalaCheck • ScalaCheck is perfect for checking our laws • defined in terms of RunnerAlg[F[ ]] forAll { (runner: Runner) => run { RunnerAlg().saveRunner(runner) *> RunnerAlg().findRunner(runner.id) }.value should ===(runner) } • instantiate this test for our instances • pure in memory, cassandra, postgres, . . . Markus Hauck (codecentric AG) Functional Web Services slide 30

Slide 33

Slide 33 text

Intro A First DSL Web Services With Http4s Runner’s Paradise DSL Final Encodings and http4s Testing Review of Final Encodings • initial encoding: pattern matching and transformations more obvious • final encoding: easier to extend, same expressiveness • btw: nice way to solve expression problem (exercise) • you can always convert between initial and final Markus Hauck (codecentric AG) Functional Web Services slide 31

Slide 34

Slide 34 text

Intro A First DSL Web Services With Http4s Runner’s Paradise DSL Final Encodings and http4s Testing Further References • Oleg Kiselyov: Lecture notes on Typed Tagless Final Interpreters • Runner’s Paradise Code: https://github.com/markus1189/runnersparadise • http4s: http://http4s.org/ Markus Hauck (codecentric AG) Functional Web Services slide 32

Slide 35

Slide 35 text

Intro A First DSL Web Services With Http4s Runner’s Paradise DSL Final Encodings and http4s Testing Questions Markus Hauck (codecentric AG) Functional Web Services slide 33