Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.
THE FUNCTIONAL WEB STACK
HTTP4S, DOOBIE & CIRCE
Gary Coady
gcoady@gilt.com
LET’S BUILD A WEBSITE
• It’s going to be CRUD (create/read/update/delete)
• How do we convert data to/from database struct...
CONVERTING TO/FROM JSON
String
Instant
Int
case class MyClass(
s: String,
t: Instant,
i: Int)
“s”@JSON String
“t”@JSON Num...
CONVERTING TO/FROM JDBC
String
Instant
Int
case class MyClass(
s: String,
t: Instant,
i: Int)
[0]@text
[1]@timestamptz
[2]...
JDBC AND JSON COMBINED
String
Instant
Int
case class MyClass(
s: String,
t: Instant,
i: Int)
[0]@text
[1]@timestamptz
[2]@...
JSON CONVERSION WITH CIRCE
• Very fast, usable JSON library
• Automatic derivation of JSON codecs for case classes
• Integ...
JSON CONVERSION WITH CIRCE
trait Encoder[A] {

def apply(a: A): Json

}
A => Json
java.time.Instant => Json
implicit val j...
JSON CONVERSION WITH CIRCE
trait Decoder[A] {

def apply(c: HCursor): Decoder.Result[A]

}
Json => A
implicit val jsonDeco...
CONVERTING CASE CLASSES WITH CIRCE
Automatic
import io.circe.generic.auto._
Manual
// for case class Person(id: Int, name:...
CONVERTING TO/FROM JSON
String
Instant
Int
case class MyClass(
s: String,
t: Instant,
i: Int)
“s”@JSON String
“t”@JSON Num...
JDBC USING DOOBIE
• Doobie is a “pure functional JDBC layer for Scala”
• Complete representation of Java JDBC API
• https:...
JDBC MAPPING WITH DOOBIE
sealed trait Meta[A] {

/** Destination JDBC types to which values of type `A` can be written. */...
JDBC MAPPING WITH DOOBIE
implicit val metaInstant =

Meta[java.sql.Timestamp].nxmap(

_.toInstant,

(i: Instant) => new ja...
CONVERTING CASE CLASSES WITH DOOBIE
Nothing extra needed!
QUERIES WITH DOOBIE
sql"select id, name from people".query[Person]
Table "public.people"
Column | Type | Modifiers
-------...
DEFINE THE EXPECTED RESULT SIZE
• myQuery.unique — Expect a single row from the query
• myQuery.option — Expect 0-1 rows f...
RUNNING QUERIES WITH DOOBIE
Define a Transactor for your Database
val xa = DriverManagerTransactor[Task](

"org.postgresql....
UPDATES WITH DOOBIE
Call .update instead of .query (returns number of rows modified)
def updatePerson(id: Int, name: String...
CONVERTING TO/FROM JDBC
String
Instant
Int
case class MyClass(
s: String,
t: Instant,
i: Int)
[0]@text
[1]@timestamptz
[2]...
JDBC AND JSON COMBINED
String
Instant
Int
case class MyClass(
s: String,
t: Instant,
i: Int)
[0]@text
[1]@timestamptz
[2]@...
HTTP4S
• http4s is a typeful, purely functional HTTP library for client and server
applications written in Scala
• http4s....
WRITING A WEB SERVICE WITH HTTP4S
type HttpService = Service[Request, Response]
represents
Request => Task[Response]
Three...
PATTERN MATCHING ON THE REQUEST
<METHOD> -> <PATH>
Extract Method and Path from Request
For example
case GET -> path
Root ...
PATTERN MATCHING ON THE REQUEST
Extract typed components with extractors
import java.util.UUID



object UUIDVar {

def un...
PARSING REQUEST BODY
Register Circe as JSON decoder
implicit def circeJsonDecoder[A](implicit decoder: Decoder[A]) =
org.h...
CREATING RESPONSE
Ok(Person(1, "Name"))
Output response code and any type with an EntityEncoder
Ok("Hello world")
Output r...
PUTTING IT ALL TOGETHER
case class Person(id: Int, firstName: String, familyName: String, registeredAt: Instant)

case cla...
PUTTING IT ALL TOGETHER
case class Person(id: Int, firstName: String, familyName: String, registeredAt: Instant)

case cla...
PUTTING IT ALL TOGETHER
object Main {

def main(args: Array[String]): Unit = {

val xa = DriverManagerTransactor[Task](

"...
ADDING HTML TEMPLATING WITH TWIRL
Add to project/plugins.sbt
addSbtPlugin("com.typesafe.sbt" % "sbt-twirl" % "1.1.1")
Plac...
PACKAGING WITH SBT-NATIVE-PACKAGER
Add to project/plugins.sbt
addSbtPlugin("com.typesafe.sbt" % "sbt-native-packager" % "1...
STREAMING DATA WITH SCALAZ-STREAM
• Response can take a scalaz-stream Process
• Doobie can create a scalaz-stream Process ...
QUESTIONS?
https://github.com/fiadliel/http4s-talk
Upcoming SlideShare
Loading in …5
×

Http4s, Doobie and Circe: The Functional Web Stack

3,252 views

Published on

Http4s, Doobie and Circe together form a nice platform for building web services. This presentations provides an introduction to using them to build your own service.

Published in: Engineering

Http4s, Doobie and Circe: The Functional Web Stack

  1. 1. THE FUNCTIONAL WEB STACK HTTP4S, DOOBIE & CIRCE Gary Coady gcoady@gilt.com
  2. 2. LET’S BUILD A WEBSITE • It’s going to be CRUD (create/read/update/delete) • How do we convert data to/from database structure? • How do we convert data to/from JSON? • Converting from one format to another is most of a developer’s job!
  3. 3. CONVERTING TO/FROM JSON String Instant Int case class MyClass( s: String, t: Instant, i: Int) “s”@JSON String “t”@JSON Number “i”@JSON Number Json.obj( “s”: Json.str, “t”: Json.number, “i”: Json.number) Case Class JSON Object
  4. 4. CONVERTING TO/FROM JDBC String Instant Int case class MyClass( s: String, t: Instant, i: Int) [0]@text [1]@timestamptz [2]@bigint JDBC ResultSet( 0: text, 1: timestamptz, 2: bigint) Case Class ResultSet
  5. 5. JDBC AND JSON COMBINED String Instant Int case class MyClass( s: String, t: Instant, i: Int) [0]@text [1]@timestamptz [2]@bigint JDBC ResultSet( 0: text, 1: timestamptz, 2: bigint) Case Class ResultSet “s”@JSON String “t”@JSON Number “i”@JSON Number JSON Object Json.obj( “s”: Json.str, “t”: Json.number, “i”: Json.number)
  6. 6. JSON CONVERSION WITH CIRCE • Very fast, usable JSON library • Automatic derivation of JSON codecs for case classes • Integration available with http4s • http://circe.io
  7. 7. JSON CONVERSION WITH CIRCE trait Encoder[A] {
 def apply(a: A): Json
 } A => Json java.time.Instant => Json implicit val jsonEncoderInstant: Encoder[Instant] =
 Encoder[Long].contramap(_.toEpochMilli)
  8. 8. JSON CONVERSION WITH CIRCE trait Decoder[A] {
 def apply(c: HCursor): Decoder.Result[A]
 } Json => A implicit val jsonDecoderInstant: Decoder[Instant] =
 Decoder[Long].map(Instant.ofEpochMilli) Json => java.time.Instant
  9. 9. CONVERTING CASE CLASSES WITH CIRCE Automatic import io.circe.generic.auto._ Manual // for case class Person(id: Int, name: String) object Person {
 implicit val decodePerson: Decoder[Person] =
 Decoder.forProduct2("id", "name")(Person.apply)
 
 implicit val encodePerson: Encoder[Person] =
 Encoder.forProduct2("id", "name")(p =>
 (p.id, p.name)
 )
 }
  10. 10. CONVERTING TO/FROM JSON String Instant Int case class MyClass( s: String, t: Instant, i: Int) “s”@JSON String “t”@JSON Number “i”@JSON Number Json.obj( “s”: Json.str, “t”: Json.number, “i”: Json.number) Case Class JSON Object
  11. 11. JDBC USING DOOBIE • Doobie is a “pure functional JDBC layer for Scala” • Complete representation of Java JDBC API • https://github.com/tpolecat/doobie • tpolecat.github.io/doobie-0.2.3/00-index.html (Documentation)
  12. 12. JDBC MAPPING WITH DOOBIE sealed trait Meta[A] {
 /** Destination JDBC types to which values of type `A` can be written. */
 def jdbcTarget: NonEmptyList[JdbcType]
 
 /** Source JDBC types from which values of type `A` can be read. */
 def jdbcSource: NonEmptyList[JdbcType]
 
 /** Constructor for a `getXXX` operation for type `A` at a given index. */
 val get: Int => RS.ResultSetIO[A] 
 /** Constructor for a `setXXX` operation for a given `A` at a given index. */
 val set: (Int, A) => PS.PreparedStatementIO[Unit] 
 
 /** Constructor for an `updateXXX` operation for a given `A` at a given index. */
 val update: (Int, A) => RS.ResultSetIO[Unit] 
 
 /** Constructor for a `setNull` operation for the primary JDBC type, at a given index. */
 val setNull: Int => PS.PreparedStatementIO[Unit]
 }
  13. 13. JDBC MAPPING WITH DOOBIE implicit val metaInstant =
 Meta[java.sql.Timestamp].nxmap(
 _.toInstant,
 (i: Instant) => new java.sql.Timestamp(i.toEpochMilli)
 ) Reusing an existing Meta definition Meta definitions exist for: Byte Short Int Boolean String Array[Byte] BigDecimal Long Float Double java.math.BigDecimal java.sql.Time java.sql.TimeStamp java.sql.Date java.util.Date
  14. 14. CONVERTING CASE CLASSES WITH DOOBIE Nothing extra needed!
  15. 15. QUERIES WITH DOOBIE sql"select id, name from people".query[Person] Table "public.people" Column | Type | Modifiers ---------------+--------------------------+----------------------------------------------------- id | integer | not null default nextval('people_id_seq'::regclass) name | text | Indexes: "people_pkey" PRIMARY KEY, btree (id) Given the table Define a query sql"select id, name from people where id = $id".query[Person] Using prepared statements & variable interpolation
  16. 16. DEFINE THE EXPECTED RESULT SIZE • myQuery.unique — Expect a single row from the query • myQuery.option — Expect 0-1 rows from the query, return an Option • myQuery.list — Return the results as a List • myQuery.vector — Return the results as a Vector • myQuery.process — Return the results as a stream of data
  17. 17. RUNNING QUERIES WITH DOOBIE Define a Transactor for your Database val xa = DriverManagerTransactor[Task](
 "org.postgresql.Driver", "jdbc:postgresql:demo", "demo", ""
 ) Run your query using the Transactor myQuery.list.transact(xa) Your query runs in a transaction (rollback on uncaught exception)
  18. 18. UPDATES WITH DOOBIE Call .update instead of .query (returns number of rows modified) def updatePerson(id: Int, name: String): ConnectionIO[Int] = sql"update people set name=$name where id=$id"
 .update def updatePerson(id: Int, name: String): ConnectionIO[Person] = sql"update people set name=$name where id=$id"
 .update
 .withUniqueGeneratedKeys("id", "name") .withUniqueGeneratedKeys provides data from updated row .withGeneratedKeys provides a stream of data, when multiple rows are updated
  19. 19. CONVERTING TO/FROM JDBC String Instant Int case class MyClass( s: String, t: Instant, i: Int) [0]@text [1]@timestamptz [2]@bigint JDBC ResultSet( 0: text, 1: timestamptz, 2: bigint) Case Class ResultSet
  20. 20. JDBC AND JSON COMBINED String Instant Int case class MyClass( s: String, t: Instant, i: Int) [0]@text [1]@timestamptz [2]@bigint JDBC ResultSet( 0: text, 1: timestamptz, 2: bigint) Case Class ResultSet “s”@JSON String “t”@JSON Number “i”@JSON Number JSON Object Json.obj( “s”: Json.str, “t”: Json.number, “i”: Json.number)
  21. 21. HTTP4S • http4s is a typeful, purely functional HTTP library for client and server applications written in Scala • http4s.org/ • https://github.com/http4s/http4s • https://github.com/TechEmpower/FrameworkBenchmarks/tree/master/ frameworks/Scala/http4s (from TechEmpower benchmarks) • www.lyranthe.org/http4s/ (some programming guides)
  22. 22. WRITING A WEB SERVICE WITH HTTP4S type HttpService = Service[Request, Response] represents Request => Task[Response] Three requirements: • Setup service • Parse request • Generate response
  23. 23. PATTERN MATCHING ON THE REQUEST <METHOD> -> <PATH> Extract Method and Path from Request For example case GET -> path Root / "people" / id Extract path components from Path
  24. 24. PATTERN MATCHING ON THE REQUEST Extract typed components with extractors import java.util.UUID
 
 object UUIDVar {
 def unapply(s: String): Option[UUID] = {
 try {
 UUID.fromString(s)
 } catch {
 case e: IllegalArgumentException =>
 None
 }
 }
 } Root / "people" / UUIDVar(uuid)
  25. 25. PARSING REQUEST BODY Register Circe as JSON decoder implicit def circeJsonDecoder[A](implicit decoder: Decoder[A]) = org.http4s.circe.jsonOf[A] req.decode[Person] { person => ... } Decode to a Person object
  26. 26. CREATING RESPONSE Ok(Person(1, "Name")) Output response code and any type with an EntityEncoder Ok("Hello world") Output response code and String as body implicit def circeJsonEncoder[A](implicit encoder: Encoder[A]) = org.http4s.circe.jsonEncoderOf[A]
  27. 27. PUTTING IT ALL TOGETHER case class Person(id: Int, firstName: String, familyName: String, registeredAt: Instant)
 case class PersonForm(firstName: String, familyName: String) object PersonDAO {
 implicit val metaInstant =
 Meta[java.sql.Timestamp].nxmap(
 _.toInstant,
 (i: Instant) => new java.sql.Timestamp(i.toEpochMilli)
 )
 
 val listPeople: ConnectionIO[List[Person]] =
 sql"select id, first_name, family_name, registered_at from people"
 .query[Person]
 .list
 
 def getPerson(id: Long): ConnectionIO[Option[Person]] =
 sql"select id, first_name, family_name, registered_at from people where id = $id"
 .query[Person]
 .option
 
 def updatePerson(id: Int, firstName: String, familyName: String): ConnectionIO[Person] =
 sql"update people set first_name=$firstName, family_name=$familyName where id=$id"
 .update
 .withUniqueGeneratedKeys("id", "first_name", "family_name", "registered_at")
 
 def insertPerson(firstName: String, familyName: String, registeredAt: Instant = Instant.now()): ConnectionIO[Person] =
 sql"insert into people (first_name, family_name, registered_at) values ($firstName, $familyName, $registeredAt)"
 .update
 .withUniqueGeneratedKeys("id", "first_name", "family_name", "registered_at")
 }
  28. 28. PUTTING IT ALL TOGETHER case class Person(id: Int, firstName: String, familyName: String, registeredAt: Instant)
 case class PersonForm(firstName: String, familyName: String) object DemoService {
 implicit def circeJsonDecoder[A](implicit decoder: Decoder[A]) = org.http4s.circe.jsonOf[A] 
 implicit def circeJsonEncoder[A](implicit encoder: Encoder[A]) = org.http4s.circe.jsonEncoderOf[A]
 
 def service(xa: Transactor[Task]) = HttpService {
 case GET -> Root / "people" =>
 Ok(PersonDAO.listPeople.transact(xa))
 
 case GET -> Root / "people" / IntVar(id) =>
 for {
 person <- PersonDAO.getPerson(id).transact(xa)
 result <- person.fold(NotFound())(Ok.apply)
 } yield result
 
 case req @ PUT -> Root / "people" / IntVar(id) =>
 req.decode[PersonForm] { form =>
 Ok(PersonDAO.updatePerson(id, form.firstName, form.familyName).transact(xa))
 }
 
 case req @ POST -> Root / "people" =>
 req.decode[PersonForm] { form =>
 Ok(PersonDAO.insertPerson(form.firstName, form.familyName).transact(xa))
 }
 }
 }
  29. 29. PUTTING IT ALL TOGETHER object Main {
 def main(args: Array[String]): Unit = {
 val xa = DriverManagerTransactor[Task](
 "org.postgresql.Driver", "jdbc:postgresql:demo", "demo", ""
 )
 
 val server =
 BlazeBuilder .bindHttp(8080)
 .mountService(DemoService.service(xa))
 .run
 
 server.awaitShutdown()
 }
 }
  30. 30. ADDING HTML TEMPLATING WITH TWIRL Add to project/plugins.sbt addSbtPlugin("com.typesafe.sbt" % "sbt-twirl" % "1.1.1") Place templates in src/main/twirl
  31. 31. PACKAGING WITH SBT-NATIVE-PACKAGER Add to project/plugins.sbt addSbtPlugin("com.typesafe.sbt" % "sbt-native-packager" % "1.1.0-RC1") Enable AutoPlugin enablePlugins(JavaServerAppPackaging)
  32. 32. STREAMING DATA WITH SCALAZ-STREAM • Response can take a scalaz-stream Process • Doobie can create a scalaz-stream Process from a Resultset • Data sent incrementally using chunked Transfer-Encoding val streamPeople: Process[ConnectionIO, Person] =
 sql"select id, first_name, family_name, registered_at from people"
 .query[Person]
 .process case GET -> Root / "stream" =>
 Ok(PersonDAO.streamPeople.transact(xa).map(p => p.id + "n"))
  33. 33. QUESTIONS? https://github.com/fiadliel/http4s-talk

×