Scala Frameworks for Web Application 2016 BizReach, Inc Scala Kansai Summit 2016 #scala_ks
Standard Frameworks for Web Application in Scala
Why we've used Play2 and Slick? ● Many users and developers ● Future prospects ● Supported by Typesafe
Dissatisfaction to Play2 ● Unnecessary features ○ View support ○ Assets management ○ Dependency injection ● Poor integration ○ Zipkin https://github.com/levkhomich/akka-tracing ○ Swagger https://github.com/swagger-api/swagger-play
Dissatisfaction to Slick ● Lerning curve ○ DBIO is hard for non-functional programmers ○ Asynchronous is not always needed ● Performance ○ Join makes subquery frequently ○ It causes performance issue with MySQL
Alternatives?
Alternatives ● Web Framework ○ Finagle ○ Akka HTTP ○ Skinny Micro ● Database Framework ○ Quill ○ doobie ○ Scalike JDBC
Finagle ● Pluggable asynchronous RPC framework based on Netty ● Client API with Circuit Breaker ● Zipkin integration ● Finatra or Finch for HTTP server ● Swagger 1.x support for Finatra (3rd party) ○ https://github.com/xiaodongw/swagger-finatra
Finagle val router = RoutingService.byPathObject[Request] { case Root => new Service[Request,Response] { def apply(req: Request): Future[Response] = { Future(Response( req.version, Status.Ok, Reader.fromBuf(Utf8("API is Ready.")) )) } } case Root / "hello"/ name => new Service[Request, Response] { def apply(req: Request): Future[Response] = { Future(Response( req.version, Status.Ok, Reader.fromBuf(Utf8(s"Hello ${name}!")) )) } } } Maybe Finatra or Finch is better choise for REST API server
Akka HTTP ● Actor-based toolkit for interacting web services and clients ● spray like routing DSL ● Reactive Streams ● Swagger 2.0 support (3rd party) ○ https://github.com/swagger-akka-http/swagger-akka-http ● Play 3.0 will move to Akka HTTP? ○ Experimental in Play 2.5 ○ https://www.playframework.com/documentation/2.5.x/AkkaHttpServer
Akka HTTP val route = get { pathEndOrSingleSlash { handleWith((request: HttpRequest) => "API is ready.") } } ~ path("hello" / ".+".r) { get { case (name) => complete { HelloResult(message = s"Hello, ${request.name}!") } } }
Skiny Micro ● Servlet-based micro framework ● Scalatra comparible routing DSL ● Future-wired async operation ● No Swagger and Zipkin support
Skiny Micro object Hello extends WebApp with JSONSupport { get("/") { "API is ready" } post("/hello/:name") { contentType = "application/json" toJSONString( HelloResult(message = s"Hello, ${params("name")}!") ) } }
Results Version View DI Routing Circuit Breaker Reactive Streams Zipkin Swagger Play2 2.5.8 Supported Guice Method + DSL - ※2 3rd party library 3rd party library Finagle 6.38.0 - ※1 DSL Supported - Supported 3rd party library Akka HTTP 2.4.10-exp erimantal - - DSL - Supported 3rd party library 3rd party library Skinny Micro 1.1.0 - - DSL - - - - ※1 Finatra is equipped Guice based dependency injection as same as Play2 ※2 There is an experimental module in Play 2.5 https://www.playframework.com/documentation/2.5.x/ReactiveStreamsIntegration
Quill ● Macro-based compile time SQL generation ○ No overhead in runtime ○ Compile time SQL validation is available ○ Some constraints in query building ● Development is very active ○ Many sub modules are available such as async, cassandra support and finagle integration ● Move to scala.meta in the future?
Quill val account: Option[Account] = ctx.run( quote { (mailAddress: String, includeRemoved: Boolean) => query[Account].filter { t => if(includeRemoved){ t.mailAddress == mailAddress } else { t.mailAddress == mailAddress && t.removed == false } } } )(mailAddress, includeRemoved).headOption
Quill val account: Option[Account] = ctx.run( quote { (mailAddress: String, includeRemoved: Boolean) => query[Account].filter { t => if(includeRemoved){ t.mailAddress == mailAddress } else { t.mailAddress == mailAddress && t.removed == false } } } )(mailAddress, includeRemoved).headOption Macro (expanded in compile time)
Quill val account: Option[Account] = ctx.run( quote { (mailAddress: String, includeRemoved: Boolean) => query[Account].filter { t => if(includeRemoved){ t.mailAddress == mailAddress } else { t.mailAddress == mailAddress && t.removed == false } } } )(mailAddress, includeRemoved).headOption Take variables as argument
Quill val account: Option[Account] = ctx.run( quote { (mailAddress: String, includeRemoved: Boolean) => query[Account].filter { t => if(includeRemoved){ t.mailAddress == mailAddress } else { t.mailAddress == mailAddress && t.removed == false } } } )(mailAddress, includeRemoved).headOption Assemble condition dynamically?
Quill SELECT ... FROM account t WHERE CASE WHEN ? THEN t.mail_address = ? ELSE (t.mail_address = ?) AND (t.removed = false) END No, translated to CASE expression
doobie ● A pure-functional JDBC layer for Scala ○ It is not an ORM ● Designed for people who are interested in: ○ typed ○ pure functional programming ● IO and monadic effects
doobie sql"select * from account where uid = $id" .query[Account] .option .transact(xa) .unsafePerformAsync { case -/(throwable) => ... case /-(account) => ... }
doobie sql"select * from account where uid = $id" .query[Account] // Query0[Account] .option // ConnectionIO[Option[Account]] .transact(xa) // Task[Option[Account]] .unsafePerformAsync { case -/(throwable) => … // Throwable case /-(account) => … // Option[Account] } Query0[Account] is all columns query that maps one returned row. Ultimately producing a value of type Option[Account].
doobie sql"select * from account where uid = $id" .query[Account] // Query0[Account] .option // ConnectionIO[Option[Account]] .transact(xa) // Task[Option[Account]] .unsafePerformAsync { case -/(throwable) => … // Throwable case /-(account) => … // Option[Account] } Task is scalaz.concurrent.Task!!
doobie ● Typechecking (experimental) ○ Validate queries against the database schema in runtime val q: Query0[Account] = sql"select * from account where uid = $id".query[Account] q.check.unsafePerformSync ✓ SQL Compiles and Typechecks ✕ C01 UID INTEGER (INTEGER) NOT NULL → String - INTEGER (INTEGER) is ostensibly coercible to String according to the JDBC specification but is not a recommended target type. Fix this by changing the schema type to CHAR or VARCHAR; or the Scala type to Int or JdbcType. ✓ C02 LOGIN_ID VARCHAR (VARCHAR) NOT NULL → String
ScalikeJDBC ● A tidy SQL-based DB access library for Scala ○ Naturally wrap JDBC APIs ○ easy-to-use ● QueryDSL is available (since 1.6)
ScalikeJDBC val id = 1 // QueryDSL val a = Account.syntax("a") val account: Option[Account] = DB readOnly { implicit s => withSQL { select.from(Account as a).where.eq(a.uid, id) }.map(Account(a)).single.apply() }
ScalikeJDBC val id = 1 case class Email(name: String, address: String) // basic SQL val email: Option[Email] = DB readOnly { implicit s => sql"select * from account where uid = ${id}" .map(rs => Email(rs.get("name"), rs.get("mail_address")) ).single.apply() }
Results Version Monad Async Mapping Typesafe DSL Genarated SQL Timing PlainSQL Slick 3.1.1 Required Always Required ※2 Supported Non-intuitiv e Runtime Supported Quill 0.10.0 Option ※1 - Supported ※3 Intuitive Compile time - doobie 0.3.0 Required Option - - - - Supported ※4 Scalike JDBC 2.4.2 - ※1 Required ※2 Supported Intuitive Runtime Supported ※1 Provides non-blocking API using postgresql-async or mysql-async ※2 A tool to generate table mappings from actual database schema is available ※3 Compile time SQL validation is available ※4 Runtime typechecking is available as experimental feature
Conclusion
Conclusion ● Web Fraemwork ○ All alternatives look good, but Play2 is not so bad as well ○ For servlet container, Skinny Micro would be good ● Database Framework ○ There is no de-facto standard library currently ○ ScalikeJDBC looks good for us and almost users

Scala Frameworks for Web Application 2016

  • 1.
    Scala Frameworks for WebApplication 2016 BizReach, Inc Scala Kansai Summit 2016 #scala_ks
  • 2.
    Standard Frameworks for WebApplication in Scala
  • 3.
    Why we've usedPlay2 and Slick? ● Many users and developers ● Future prospects ● Supported by Typesafe
  • 4.
    Dissatisfaction to Play2 ●Unnecessary features ○ View support ○ Assets management ○ Dependency injection ● Poor integration ○ Zipkin https://github.com/levkhomich/akka-tracing ○ Swagger https://github.com/swagger-api/swagger-play
  • 5.
    Dissatisfaction to Slick ●Lerning curve ○ DBIO is hard for non-functional programmers ○ Asynchronous is not always needed ● Performance ○ Join makes subquery frequently ○ It causes performance issue with MySQL
  • 6.
  • 7.
    Alternatives ● Web Framework ○Finagle ○ Akka HTTP ○ Skinny Micro ● Database Framework ○ Quill ○ doobie ○ Scalike JDBC
  • 8.
    Finagle ● Pluggable asynchronousRPC framework based on Netty ● Client API with Circuit Breaker ● Zipkin integration ● Finatra or Finch for HTTP server ● Swagger 1.x support for Finatra (3rd party) ○ https://github.com/xiaodongw/swagger-finatra
  • 9.
    Finagle val router =RoutingService.byPathObject[Request] { case Root => new Service[Request,Response] { def apply(req: Request): Future[Response] = { Future(Response( req.version, Status.Ok, Reader.fromBuf(Utf8("API is Ready.")) )) } } case Root / "hello"/ name => new Service[Request, Response] { def apply(req: Request): Future[Response] = { Future(Response( req.version, Status.Ok, Reader.fromBuf(Utf8(s"Hello ${name}!")) )) } } } Maybe Finatra or Finch is better choise for REST API server
  • 10.
    Akka HTTP ● Actor-basedtoolkit for interacting web services and clients ● spray like routing DSL ● Reactive Streams ● Swagger 2.0 support (3rd party) ○ https://github.com/swagger-akka-http/swagger-akka-http ● Play 3.0 will move to Akka HTTP? ○ Experimental in Play 2.5 ○ https://www.playframework.com/documentation/2.5.x/AkkaHttpServer
  • 11.
    Akka HTTP val route= get { pathEndOrSingleSlash { handleWith((request: HttpRequest) => "API is ready.") } } ~ path("hello" / ".+".r) { get { case (name) => complete { HelloResult(message = s"Hello, ${request.name}!") } } }
  • 12.
    Skiny Micro ● Servlet-basedmicro framework ● Scalatra comparible routing DSL ● Future-wired async operation ● No Swagger and Zipkin support
  • 13.
    Skiny Micro object Helloextends WebApp with JSONSupport { get("/") { "API is ready" } post("/hello/:name") { contentType = "application/json" toJSONString( HelloResult(message = s"Hello, ${params("name")}!") ) } }
  • 14.
    Results Version View DIRouting Circuit Breaker Reactive Streams Zipkin Swagger Play2 2.5.8 Supported Guice Method + DSL - ※2 3rd party library 3rd party library Finagle 6.38.0 - ※1 DSL Supported - Supported 3rd party library Akka HTTP 2.4.10-exp erimantal - - DSL - Supported 3rd party library 3rd party library Skinny Micro 1.1.0 - - DSL - - - - ※1 Finatra is equipped Guice based dependency injection as same as Play2 ※2 There is an experimental module in Play 2.5 https://www.playframework.com/documentation/2.5.x/ReactiveStreamsIntegration
  • 15.
    Quill ● Macro-based compiletime SQL generation ○ No overhead in runtime ○ Compile time SQL validation is available ○ Some constraints in query building ● Development is very active ○ Many sub modules are available such as async, cassandra support and finagle integration ● Move to scala.meta in the future?
  • 16.
    Quill val account: Option[Account]= ctx.run( quote { (mailAddress: String, includeRemoved: Boolean) => query[Account].filter { t => if(includeRemoved){ t.mailAddress == mailAddress } else { t.mailAddress == mailAddress && t.removed == false } } } )(mailAddress, includeRemoved).headOption
  • 17.
    Quill val account: Option[Account]= ctx.run( quote { (mailAddress: String, includeRemoved: Boolean) => query[Account].filter { t => if(includeRemoved){ t.mailAddress == mailAddress } else { t.mailAddress == mailAddress && t.removed == false } } } )(mailAddress, includeRemoved).headOption Macro (expanded in compile time)
  • 18.
    Quill val account: Option[Account]= ctx.run( quote { (mailAddress: String, includeRemoved: Boolean) => query[Account].filter { t => if(includeRemoved){ t.mailAddress == mailAddress } else { t.mailAddress == mailAddress && t.removed == false } } } )(mailAddress, includeRemoved).headOption Take variables as argument
  • 19.
    Quill val account: Option[Account]= ctx.run( quote { (mailAddress: String, includeRemoved: Boolean) => query[Account].filter { t => if(includeRemoved){ t.mailAddress == mailAddress } else { t.mailAddress == mailAddress && t.removed == false } } } )(mailAddress, includeRemoved).headOption Assemble condition dynamically?
  • 20.
    Quill SELECT ... FROM accountt WHERE CASE WHEN ? THEN t.mail_address = ? ELSE (t.mail_address = ?) AND (t.removed = false) END No, translated to CASE expression
  • 21.
    doobie ● A pure-functionalJDBC layer for Scala ○ It is not an ORM ● Designed for people who are interested in: ○ typed ○ pure functional programming ● IO and monadic effects
  • 22.
    doobie sql"select * fromaccount where uid = $id" .query[Account] .option .transact(xa) .unsafePerformAsync { case -/(throwable) => ... case /-(account) => ... }
  • 23.
    doobie sql"select * fromaccount where uid = $id" .query[Account] // Query0[Account] .option // ConnectionIO[Option[Account]] .transact(xa) // Task[Option[Account]] .unsafePerformAsync { case -/(throwable) => … // Throwable case /-(account) => … // Option[Account] } Query0[Account] is all columns query that maps one returned row. Ultimately producing a value of type Option[Account].
  • 24.
    doobie sql"select * fromaccount where uid = $id" .query[Account] // Query0[Account] .option // ConnectionIO[Option[Account]] .transact(xa) // Task[Option[Account]] .unsafePerformAsync { case -/(throwable) => … // Throwable case /-(account) => … // Option[Account] } Task is scalaz.concurrent.Task!!
  • 25.
    doobie ● Typechecking (experimental) ○Validate queries against the database schema in runtime val q: Query0[Account] = sql"select * from account where uid = $id".query[Account] q.check.unsafePerformSync ✓ SQL Compiles and Typechecks ✕ C01 UID INTEGER (INTEGER) NOT NULL → String - INTEGER (INTEGER) is ostensibly coercible to String according to the JDBC specification but is not a recommended target type. Fix this by changing the schema type to CHAR or VARCHAR; or the Scala type to Int or JdbcType. ✓ C02 LOGIN_ID VARCHAR (VARCHAR) NOT NULL → String
  • 26.
    ScalikeJDBC ● A tidySQL-based DB access library for Scala ○ Naturally wrap JDBC APIs ○ easy-to-use ● QueryDSL is available (since 1.6)
  • 27.
    ScalikeJDBC val id =1 // QueryDSL val a = Account.syntax("a") val account: Option[Account] = DB readOnly { implicit s => withSQL { select.from(Account as a).where.eq(a.uid, id) }.map(Account(a)).single.apply() }
  • 28.
    ScalikeJDBC val id =1 case class Email(name: String, address: String) // basic SQL val email: Option[Email] = DB readOnly { implicit s => sql"select * from account where uid = ${id}" .map(rs => Email(rs.get("name"), rs.get("mail_address")) ).single.apply() }
  • 29.
    Results Version Monad AsyncMapping Typesafe DSL Genarated SQL Timing PlainSQL Slick 3.1.1 Required Always Required ※2 Supported Non-intuitiv e Runtime Supported Quill 0.10.0 Option ※1 - Supported ※3 Intuitive Compile time - doobie 0.3.0 Required Option - - - - Supported ※4 Scalike JDBC 2.4.2 - ※1 Required ※2 Supported Intuitive Runtime Supported ※1 Provides non-blocking API using postgresql-async or mysql-async ※2 A tool to generate table mappings from actual database schema is available ※3 Compile time SQL validation is available ※4 Runtime typechecking is available as experimental feature
  • 30.
  • 31.
    Conclusion ● Web Fraemwork ○All alternatives look good, but Play2 is not so bad as well ○ For servlet container, Skinny Micro would be good ● Database Framework ○ There is no de-facto standard library currently ○ ScalikeJDBC looks good for us and almost users