Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Brighton Java: Day in the life...

Brighton Java: Day in the life...

Richard Dallaway

May 31, 2017
Tweet

More Decks by Richard Dallaway

Other Decks in Programming

Transcript

  1. $ http GET :8080/customers HTTP/1.1 200 OK Content-Type: application/json [

    { "id": 1, "name": "Alice", "phone": "+1 555 1234" }, { "id": 2, "name": "Bob", "phone": "+1 555 5678" } ]
  2. sealed trait Subscription case object Never extends Subscription case object

    Active extends Subscription case object Lapsed extends Subscription
  3. sealed trait Subscription case object Never extends Subscription case class

    Active(expires: Date) extends Subscription case object Lapsed extends Subscription
  4. sealed trait Subscription case object Never extends Subscription case class

    Active(expires: Date) extends Subscription case object Lapsed extends Subscription case class Customer( id : Long, name : String, phone : String, sub : Subscription )
  5. sealed trait Subscription case object Never extends Subscription case class

    Active(expires: Date) extends Subscription case object Lapsed extends Subscription case class Customer( id : Long, name : String, phone : String, sub : Subscription ) def transition(sub: Subscription, at: Date): Subscription = ???
  6. sealed trait Subscription case object Never extends Subscription case class

    Active(expires: Date) extends Subscription case object Lapsed extends Subscription case class Customer( id : Long, name : String, phone : String, sub : Subscription ) def transition(sub: Subscription, at: Date): Subscription = sub match { case Lapsed | Never => sub case Active(exp) => ??? }
  7. sealed trait Subscription case object Never extends Subscription case class

    Active(expires: Date) extends Subscription case object Lapsed extends Subscription case class Customer( id : Long, name : String, phone : String, sub : Subscription ) def transition(sub: Subscription, at: Date): Subscription = sub match { case Lapsed | Never => sub case Active(exp) => if (at > exp) Lapsed else sub }
  8. “Marke ng just reminded me we give free subscrip ons

    to some customers” — Product Owner
  9. sealed trait Subscription case object Never extends Subscription case class

    Active(expires: Date) extends Subscription case object Lapsed extends Subscription
  10. sealed trait Subscription case object Never extends Subscription case class

    Active(expires: Date) extends Subscription case object Lapsed extends Subscription case object Freebie extends Subscription def transition(sub: Subscription, at: Date): Subscription = sub match { case Lapsed | Never => sub case Active(exp) => if (at > exp) Lapsed else sub } ERROR: non‐exhaus ve match
  11. def transition(c: Customer, at: Date): Action = ??? sealed trait

    Action case object NoAction extends Action case class EncourageRenewal(c: Customer) extends Action case class Expire(c: Customer) extends Action ...
  12. def transition(c: Customer, at: Date): Action = ??? sealed trait

    Action case object NoAction extends Action case class EncourageRenewal(c: Customer) extends Action case class Expire(c: Customer) extends Action ... def perform(a: Action): Unit = a match { case NoAction => case Expire(c) => sendSMS(c.phone) ...etc }
  13. Output JSON to the Client { "id": 1, "name": "Alice",

    "phone": "+1 555 1234", "sub": { "type": "expired" } }
  14. Turn Data into JSON text 7.toString 7 "\"" + "Hello"

    + "\"" "Hello" { greeting: "Hello", count: 7 }
  15. Turn Data into JSON text 7.toString 7 "\"" + "Hello"

    + "\"" "Hello" { greeting: "Hello", count: 7 } Int => String String => String
  16. Turn Data into JSON text 7.toString 7 "\"" + "Hello"

    + "\"" "Hello" { greeting: "Hello", count: 7 } Int => String String => String T => String
  17. trait JsonFormat[T] { def format(value: T): String } val intFormat

    = new JsonFormat[Int] { def format(value: Int): String = value.toString } val strFormat = new JsonFormat[String] { def format(value: String): String = "\"" + value + "\"" }
  18. trait JsonFormat[T] { def format(value: T): String } val intFormat

    = new JsonFormat[Int] { def format(value: Int): String = value.toString } val strFormat = new JsonFormat[String] { def format(value: String): String = "\"" + value + "\"" } def outputJson[T](value: T)(jf: JsonFormat[T]) = jf.format(value) outputJson(7)(intFormat) // 7
  19. trait JsonFormat[T] { def format(value: T): String } implicit val

    intFormat = new JsonFormat[Int] { def format(value: Int): String = value.toString } implicit val strFormat = new JsonFormat[String] { def format(value: String): String = "\"" + value + "\"" } def outputJson[T](value: T)(implicit jf: JsonFormat[T]) = jf.format(value) outputJson(7) // 7
  20. implicit def listFormat[T] = new JsonFormat[List[T]] { def format(values: List[T]):

    String = { val formattedValues = { ??? } "[" + formattedValues.mkString(",") + "]" } }
  21. implicit def listFormat[T] = new JsonFormat[List[T]] { def format(values: List[T]):

    String = { val formattedValues = for { v <- values } yield ??? "[" + formattedValues.mkString(",") + "]" } }
  22. implicit def listFormat[T](implicit jf: JsonFormat[T]) = new JsonFormat[List[T]] { def

    format(values: List[T]): String = { val formattedValues = for { v <- values } yield jf.format(v) "[" + formattedValues.mkString(",") + "]" } }
  23. implicit def listFormat[T](implicit jf: JsonFormat[T]) = new JsonFormat[List[T]] { def

    format(values: List[T]): String = { val formattedValues = for { v <- values } yield jf.format(v) "[" + formattedValues.mkString(",") + "]" } } def outputJson[T](value: T)(implicit jf: JsonFormat[T]) = jf.format(value) outputJson( List(1,2,3,4) ) [1,2,3,4]
  24. implicit def listFormat[T](implicit jf: JsonFormat[T]) = new JsonFormat[List[T]] { def

    format(values: List[T]): String = { val formattedValues = for { v <- values } yield jf.format(v) "[" + formattedValues.mkString(",") + "]" } } def outputJson[T](value: T)(implicit jf: JsonFormat[T]) = jf.format(value) outputJson( List(1,2,3,4) ) [1,2,3,4] outputJson( List( List(1,2), List(3,4) ) ) [ [1,2], [3,4] ]
  25. case class Customer( name : String, phone : String, id

    : Long ) sql" select name, phone, id from customer ".query[Customer]
  26. case class Customer( name : String, phone : String, id

    : Long ) sql" select name, phone, id from customer ".query[Customer] implicit val SubscriptionMeta: Meta[Subscription] = Meta[String].nxmap(ch => ch match { case "A" => Active case "L" => Lapsed case "N" => Never }, sub => sub match { case Active => "A" case Lapsed => "L" case Never => "N" })
  27. class CustomerAPI(db: Database) { val service = HttpService { case

    GET -> Root / "customers" => Ok(db.list) case GET -> Root / "customers" / IntVar(id) => db.find(id).flatMap { case Some(customer) => Ok(customer) case None => NotFound() } } }
  28. class CustomerAPI(db: Database) { val service = HttpService { case

    GET -> Root / "customers" => Ok(db.list) case GET -> Root / "customers" / IntVar(id) => db.find(id).flatMap { case Some(customer) => Ok(customer) case None => NotFound() } case PATCH -> Root / "customers" / IntVar(id) => decode the patch lookup the customer in the database found a record? somehow apply the patch to the record update the database otherwise, 404 the request } }
  29. Simplified Patch { "name" : "Robert" } or { "name"

    : "Robert", "phone : "+1 555 555" }
  30. case class Customer( id : Long, name : String, phone

    : String ) case class CustomerPatch( name : Option[String], phone : Option[String] )
  31. case class Customer( id : Long, name : String, phone

    : String ) case class CustomerPatch( name : Option[String], phone : Option[String] ) trait Merge[Into, From] { def merge(i: Into, f: From): Into } def merge[Into, From](i: Into, f: From) = ???
  32. case class Customer( id : Long, name : String, phone

    : String ) case class CustomerPatch( name : Option[String], phone : Option[String] ) trait Merge[Into, From] { def merge(i: Into, f: From): Into } def merge[Into, From](i: Into, f: From) (implicit m: Merge[Into, From]) = m.merge(i, f)
  33. case class Customer( id : Long, name : String, phone

    : String ) case class CustomerPatch( name : Option[String], phone : Option[String] ) trait Merge[Into, From] { def merge(i: Into, f: From): Into } def merge[Into, From](i: Into, f: From) (implicit m: Merge[Into, From]) = m.merge(i, f) implicit def customerMerge: Merge[Customer, CustomerPatch] = ???
  34. case class Customer( id : Long, name : String, phone

    : String ) case class CustomerPatch( name : Option[String], phone : Option[String] ) trait Merge[Into, From] { def merge(i: Into, f: From): Into } def merge[Into, From](i: Into, f: From) (implicit m: Merge[Into, From]) = m.merge(i, f) implicit def customerMerge: Merge[Customer, CustomerPatch] = ??? implicit def optMerge = new Merge[String, Option[String]] { def merge(initial: String, update: Option[String]) = update match { case Some(value) => value case None => initial } }
  35. case class Customer( id : Long, name : String, phone

    : String ) case class CustomerPatch( name : Option[String], phone : Option[String] ) trait Merge[Into, From] { def merge(i: Into, f: From): Into } def merge[Into, From](i: Into, f: From) (implicit m: Merge[Into, From]) = m.merge(i, f) implicit def customerMerge: Merge[Customer, CustomerPatch] = ??? merge( Customer(1, "Bob", "555-123"), CustomerPatch(Some("Robert"), None) ) Customer(1, "Robert", "555-123")
  36. Pu ng it all together... case req @ PATCH ->

    Root / "customers" / IntVar(id) => req.decode[CustomerPatch] { patch => db.find(id).flatMap { case None => NotFound() case Some(customer) => val updated = merge(customer, patch) Ok(db.update(updated)) } } val merge = bulletin.AutoMerge[Customer, CustomerPatch] case class CustomerPatch( name : Option[String], phone : Option[String] )