Transcript

Scala Frameworks forWeb Application 2016

BizReach, IncScala 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-experimantal

- - 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 tWHERE 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

runtimeval 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

// QueryDSLval 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 SQLval 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-intuitive

Runtime Supported

Quill 0.10.0 Option ※1 - Supported※3

Intuitive Compile time

-

doobie 0.3.0 Required Option - - - - Supported※4

ScalikeJDBC

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