Browse Source

Update play2-scala-anorm test

- Update to Play 2.3
- Update to Scala 2.11
- Update other libraries
- Refactor and to use code shared by Slick
Rich Dougherty 11 years ago
parent
commit
cd9703fe3b

+ 0 - 20
frameworks/Scala/play2-scala/play2-scala-anorm/README.md

@@ -1,20 +0,0 @@
-#Play Benchmarking Test
-
-This is the Play portion of a [benchmarking test suite](../) comparing a variety of web development platforms.
-
-### Data-Store/Database Mapping Test
-
-* [Database test controller](app/controllers/Application.scala)
-* [Database test model](app/models/World.scala)
-
-## Infrastructure Software Versions
-The tests were run with:
-
-* [Java OpenJDK 1.7.0_09](http://openjdk.java.net/)
-* [Play 2](http://http://www.playframework.com/)
-
-## Test URLs
-
-### Data-Store/Database Mapping Test
-
-http://localhost/db?queries=5

+ 48 - 65
frameworks/Scala/play2-scala/play2-scala-anorm/app/controllers/Application.scala

@@ -8,6 +8,7 @@ import java.util.concurrent._
 import scala.concurrent._
 import models.{World, Fortune}
 import utils._
+import utils.DbOperation._
 import scala.concurrent.Future
 
 import play.api.libs.concurrent.Execution.Implicits._
@@ -15,40 +16,60 @@ import play.core.NamedThreadFactory
 
 object Application extends Controller {
 
-  private val TestDatabaseRows = 10000
+  // Anorm code
 
-  private val partitionCount = current.configuration.getInt("db.default.partitionCount").getOrElse(2)
-  private val maxConnections =
-    partitionCount * current.configuration.getInt("db.default.maxConnectionsPerPartition").getOrElse(5)
-  private val minConnections =
-    partitionCount * current.configuration.getInt("db.default.minConnectionsPerPartition").getOrElse(5)
+  def getRandomWorlds(n: Int): Future[Seq[World]] = asyncDbOp { implicit connection =>
+    val random = ThreadLocalRandom.current()
+    for (_ <- 1 to n) yield {
+      val randomId: Long = random.nextInt(TestDatabaseRows) + 1
+      World.findById(randomId)
+    }
+  }
+
+  def getFortunes(): Future[Seq[Fortune]] = asyncDbOp { implicit connection =>
+    Fortune.getAll()
+  }
+
+  def updateWorlds(n: Int): Future[Seq[World]] = asyncDbOp { implicit connection =>
+    val random = ThreadLocalRandom.current()
+    for(_ <- 1 to n) yield {
+      val randomId: Long = random.nextInt(TestDatabaseRows) + 1
+      val world = World.findById(random.nextInt(TestDatabaseRows) + 1)
+      val randomNumber: Long = random.nextInt(10000) + 1
+      val updatedWorld = world.copy(randomNumber = randomNumber)
+      World.updateRandom(updatedWorld)
+      updatedWorld
+    }
+  }
 
-  private val tpe = new ThreadPoolExecutor(minConnections, maxConnections,
-    0L, TimeUnit.MILLISECONDS,
-    new LinkedBlockingQueue[Runnable](),
-    new NamedThreadFactory("dbEc"))
-  private val dbEc = ExecutionContext.fromExecutorService(tpe)
+  // Common code between Anorm and Slick
 
-  // If the thread-pool used by the database grows too large then our server
-  // is probably struggling, and we should start dropping requests. Set
-  // the max size of our queue something above the number of concurrent
-  // connections that we need to handle.
-  def isDbQueueTooBig: Boolean = tpe.getQueue.size() <= 1024
+  protected val TestDatabaseRows = 10000
 
-  def db = PredicatedAction(isDbQueueTooBig, ServiceUnavailable) {
-    Action.async {
-      getRandomWorlds(1).map { worlds =>
-        Ok(Json.toJson(worlds.head))
-      }
+  def db = Action.async {
+    getRandomWorlds(1).map { worlds =>
+      Ok(Json.toJson(worlds.head))
     }
   }
 
-  def queries(countString: String) = PredicatedAction(isDbQueueTooBig, ServiceUnavailable) {
-    Action.async {
-      val n = parseCount(countString)
-      getRandomWorlds(n).map { worlds =>
-        Ok(Json.toJson(worlds))
-      }
+  def queries(countString: String) = Action.async {
+    val n = parseCount(countString)
+    getRandomWorlds(n).map { worlds =>
+      Ok(Json.toJson(worlds))
+    }
+  }
+
+  def fortunes() = Action.async {
+    getFortunes().map { dbFortunes =>
+      val appendedFortunes =  Fortune(0, "Additional fortune added at request time.") :: (dbFortunes.to[List])
+      Ok(views.html.fortune(appendedFortunes))
+    }
+  }
+
+  def update(queries: String) = Action.async {
+    val n = parseCount(queries)
+    updateWorlds(n).map { worlds =>
+      Ok(Json.toJson(worlds))
     }
   }
 
@@ -65,42 +86,4 @@ object Application extends Controller {
     }
   }
 
-  private def getRandomWorlds(n: Int): Future[Seq[World]] = Future {
-    val random = ThreadLocalRandom.current()
-    DB.withConnection { implicit connection =>
-      for (_ <- 1 to n) yield {
-        val randomId: Long = random.nextInt(TestDatabaseRows) + 1
-        World.findById(randomId)
-      }
-    }
-  }(dbEc)
-
-  def fortunes() = PredicatedAction(isDbQueueTooBig, ServiceUnavailable) {
-    Action.async {
-      Future {
-        val fortunes = Fortune.getAll()
-        val extendedFortunes = Fortune(0.toLong, "Additional fortune added at request time.") +: fortunes
-        Ok(views.html.fortune(extendedFortunes))
-      }
-    }
-  }
-
-  def update(countString: String) = PredicatedAction(isDbQueueTooBig, ServiceUnavailable) {
-    Action.async {
-      val n = parseCount(countString)
-      Future {
-        val random = ThreadLocalRandom.current()
-        val worlds = DB.withConnection { implicit connection =>
-          for(_ <- 1 to n) yield {
-            val randomId: Long = random.nextInt(TestDatabaseRows) + 1
-            val world = World.findById(random.nextInt(TestDatabaseRows) + 1)
-            val updatedWorld = world.copy(randomNumber = random.nextInt(10000) + 1)
-            World.updateRandom(updatedWorld)
-            updatedWorld
-          }
-        }
-        Ok(Json.toJson(worlds)).withHeaders("Server" -> "Netty")
-      }(dbEc)
-    }
-  }
 }

+ 5 - 14
frameworks/Scala/play2-scala/play2-scala-anorm/app/models/World.scala

@@ -8,7 +8,7 @@ import play.api.libs.functional.syntax._
 import play.api.libs.json._
 import play.api.Play.current
 
-case class World(id: Pk[Long], randomNumber: Long)
+case class World(id: Id[Long], randomNumber: Long)
 
 object World {
     /**
@@ -27,9 +27,9 @@ object World {
    * Parse a World from a ResultSet
    */
   val simpleRowParser = {
-    get[Pk[Long]]("world.id") ~
+    get[Long]("world.id") ~
     get[Long]("world.randomNumber") map {
-      case id~randomNumber => World(id, randomNumber)
+      case id~randomNumber => World(Id(id), randomNumber)
     }
   }
 
@@ -37,19 +37,10 @@ object World {
    * Retrieve a World by id.
    */
   def findById(id: Long)(implicit connection: Connection): World = {
-    DB.withConnection { implicit connection =>
-      SQL("SELECT * FROM World WHERE id = {id}").on(
-          "id" -> id
-      ).as(World.simpleRowParser.single)
-    }
+    SQL"SELECT * FROM World WHERE id = ${id}".as(World.simpleRowParser.single)
   }
 
   def updateRandom(world: World)(implicit connection: Connection) {
-    DB.withConnection { implicit connection =>
-      SQL("UPDATE World SET randomNumber = {randomNumber} WHERE id = {id}").on(
-        "id" -> world.id.get,
-        "randomNumber" -> world.randomNumber
-      ).executeUpdate()
-    }
+    SQL"UPDATE World SET randomNumber = ${world.randomNumber} WHERE id = ${world.id.get}".executeUpdate()
   }
 }

+ 47 - 0
frameworks/Scala/play2-scala/play2-scala-anorm/app/utils/DbOperation.scala

@@ -0,0 +1,47 @@
+package utils
+
+import java.sql.Connection
+import java.util.concurrent._
+import play.api.db.DB
+import play.api.Play.current
+import play.core.NamedThreadFactory
+import scala.concurrent._
+import scala.concurrent.Future
+
+object DbOperation {
+
+  // Common code between Anorm and Slick
+
+  private val maxDbOperations = current.configuration.underlying.getInt("max-db-ops")
+
+  private val partitionCount = current.configuration.getInt("db.default.partitionCount").getOrElse(2)
+  private val maxConnections =
+    partitionCount * current.configuration.getInt("db.default.maxConnectionsPerPartition").getOrElse(5)
+  private val minConnections =
+    partitionCount * current.configuration.getInt("db.default.minConnectionsPerPartition").getOrElse(5)
+
+  private val tpe = new ThreadPoolExecutor(minConnections, maxConnections,
+    0L, TimeUnit.MILLISECONDS,
+    new LinkedBlockingQueue[Runnable](), // TODO: Could use ArrayBlockingQueue?
+    new NamedThreadFactory("dbEc"))
+  private val dbEc = ExecutionContext.fromExecutorService(tpe)
+
+  // Anorm code
+
+  /**
+   * Run a DB operation in the DB context. Automatically
+   * provides a Session.
+   */
+  def asyncDbOp[T](op: Connection => T): Future[T] = {
+    // If the thread-pool queue used by the database grows too large then our server
+    // is probably struggling, and we should start dropping requests. If we don't
+    // then we'll just slow everything down and it will fail anyway. Better to fail
+    // quickly rather than slowly. Set the max size of our queue something above the
+    // number of concurrent connections that we expect to be handling.
+    if (tpe.getQueue.size > maxDbOperations) sys.error(s"Aborted DB operation because queue is > $maxDbOperations")
+    Future {
+      DB.withConnection { connection => op(connection) }
+    }(dbEc)
+  }
+
+}

+ 0 - 20
frameworks/Scala/play2-scala/play2-scala-anorm/app/utils/PredicatedAction.scala

@@ -1,20 +0,0 @@
-package utils
-
-import play.api.mvc._
-import scala.concurrent.{Promise, Future}
-
-/**
- * A predicated action is one where a condition must be satisfied in order to proceed with the request. If the
- * condition is not satisfied then a supplied status result is yielded.
- */
-class PredicatedActionBuilder {
-  def apply[A](p: => Boolean, failed: => SimpleResult)(action: Action[A]): Action[A] = new Action[A] {
-    def apply(request: Request[A]): Future[SimpleResult] = {
-      if (p) action(request) else Promise.successful(failed).future
-    }
-
-    lazy val parser = action.parser
-  }
-}
-
-object PredicatedAction extends PredicatedActionBuilder

+ 6 - 3
frameworks/Scala/play2-scala/play2-scala-anorm/build.sbt

@@ -2,8 +2,11 @@ name := "play2-scala-anorm"
 
 version := "1.0-SNAPSHOT"
 
-libraryDependencies ++= Seq(jdbc, anorm, "mysql" % "mysql-connector-java" % "5.1.22")
+scalaVersion := "2.11.2"
 
-dependencyOverrides += "com.jolbox" % "bonecp" % "0.7.1.RELEASE"
+lazy val root = (project in file(".")).enablePlugins(PlayScala)
 
-playScalaSettings
+libraryDependencies ++= Seq(
+  jdbc,
+  anorm,
+  "mysql" % "mysql-connector-java" % "5.1.32")

+ 4 - 1
frameworks/Scala/play2-scala/play2-scala-anorm/conf/application.conf

@@ -1,6 +1,9 @@
 # This is the main configuration file for the application.
 # ~~~~~
 
+# The max size of the queue for running db operations
+max-db-ops = 1024
+
 # Secret key
 # ~~~~~
 # The secret key is used to secure cryptographics functions.
@@ -30,7 +33,7 @@ application.langs="en"
 # You can expose this datasource via JNDI if needed (Useful for JPA)
 # db.default.jndiName=DefaultDS
 db.default.driver= com.mysql.jdbc.Driver
-db.default.url="jdbc:mysql://!!DATABASE_HOST!!:3306/hello_world?jdbcCompliantTruncation=false&elideSetAutoCommits=true&useLocalSessionState=true&cachePrepStmts=true&cacheCallableStmts=true&alwaysSendSetIsolation=false&prepStmtCacheSize=4096&cacheServerConfiguration=true&prepStmtCacheSqlLimit=2048&zeroDateTimeBehavior=convertToNull&traceProtocol=false&useUnbufferedInput=false&useReadAheadInput=false&maintainTimeStats=false&useServerPrepStmts&cacheRSMetadata=true"
+db.default.url="jdbc:mysql://localhost:3306/hello_world?jdbcCompliantTruncation=false&elideSetAutoCommits=true&useLocalSessionState=true&cachePrepStmts=true&cacheCallableStmts=true&alwaysSendSetIsolation=false&prepStmtCacheSize=4096&cacheServerConfiguration=true&prepStmtCacheSqlLimit=2048&zeroDateTimeBehavior=convertToNull&traceProtocol=false&useUnbufferedInput=false&useReadAheadInput=false&maintainTimeStats=false&useServerPrepStmts&cacheRSMetadata=true"
 db.default.user=benchmarkdbuser
 db.default.password=benchmarkdbpass
 db.default.jndiName=DefaultDS

+ 1 - 1
frameworks/Scala/play2-scala/play2-scala-anorm/project/build.properties

@@ -1 +1 @@
-sbt.version=0.13.0
+sbt.version=0.13.5

+ 1 - 1
frameworks/Scala/play2-scala/play2-scala-anorm/project/plugins.sbt

@@ -5,4 +5,4 @@ logLevel := Level.Warn
 resolvers += "Typesafe repository" at "http://repo.typesafe.com/typesafe/releases/"
 
 // Use the Play sbt plugin for Play projects
-addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.2.0")
+addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.3.3")