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