【发布时间】:2015-03-18 01:43:43
【问题描述】:
我最近开始在 Play Scala 中开发应用程序。虽然我已经在多个应用程序中使用过 Play Java,但我对 Scala 和 Play Scala 还是很陌生。
我使用 DAO 模式来抽象数据库交互。 DAO 包含用于插入、更新删除的方法。在阅读了异步和线程池相关文档后,我认为使数据库交互异步非常重要,除非您将 Play 默认线程池调整为拥有多个线程。
为了确保所有数据库调用都是异步处理的,我进行了所有调用以返回 Future 而不是直接返回值。我为数据库交互创建了一个单独的执行上下文。
trait Dao[K, V] {
def findById(id: K): Future[Option[V]]
def update(v: V): Future[Boolean]
[...]
}
这导致动作中的代码非常复杂且深度嵌套。
trait UserDao extends Dao[Long, User] {
def existsWithEmail(email: String): Future[Boolean]
def insert(u: User) Future[Boolean]
}
object UserController extends Controller {
def register = Action {
[...]
userDao.existsWithEmail(email).flatMap { exists =>
exits match {
case true =>
userDao.insert(new User("foo", "bar")).map { created =>
created match {
case true => Ok("Created!")
case false => BadRequest("Failed creation")
}
}
case false =>
Future(BadRequest("User exists with same email"))
}
}
}
}
以上是最简单的操作示例。随着我涉及更多的数据库调用,嵌套级别变得更深。虽然我认为使用 for 理解可以减少一些嵌套,但我怀疑我的方法本身是否从根本上是错误的?
考虑我需要创建用户的情况,
一个。如果没有相同的电子邮件地址已经存在。
b.如果没有相同的手机号码已经存在。
我可以创造两个未来,
f(a) 通过电子邮件检查用户是否存在。
f(b) 检查用户是否使用移动设备。
除非我验证这两个条件都为 false,否则我无法插入新用户。我实际上可以让 f(a) 和 f(b) 并行运行。如果 f(a) 评估为真,则并行执行可能是不可取的,否则可能会有利。创建用户的第 3 步取决于这两个未来,所以我想知道跟随是否同样好?
trait UserDao extends Dao[Long, User] {
def existsWithEmail(email: String): Boolean
def existsWithMobile(mobile: String): Boolean
def insert(u: User): Unit
}
def register = Action {
implicit val dbExecutionContext = myconcurrent.Context.dbExceutionContext
Future {
if (!userDao.existsWithEmail(email) && !userDao.existsWithMobile(mobile) {
userDao.insert(new User("foo", "bar")
Ok("Created!")
} else {
BadRequest("Already exists!")
}
}
}
哪种方法更好?使用单个 Future 多次调用数据库的方法有什么缺点吗?
【问题讨论】:
-
您可以使用
for/yield糖更轻松地处理返回期货的连续调用。此外,当您的整个地图功能是模式匹配时,您可以直接放入案例,即myFuture.flatMap { case ... }。像traverse(来自scalaz)这样的高阶函数也可以派上用场。我不知道 Play 是否与 Spray 的onComplete有任何类比(这使得在该框架中使用Futures 非常容易)。对数据库进行多次调用的单个 future 可能很好,但往往会导致将业务逻辑放在更难测试的 dao 中。 -
坦率地说,在您确定数据库访问是您的瓶颈之前,我不会为此烦恼。
-
@lmm 我不是说把业务逻辑放到Dao里。我的意思是在动作中创建一个 Future,然后在其中嵌入多个调用。
for / yield当调用不相互依赖时才有意义。没有? -
for/yield当调用相互依赖时更有意义,IMO,尽管它在任何一种情况下都有效。如果您将Future创建放在Actions 中,那么这可能比将其放在 dao 中更重复,因为您可能会有许多不同的Actions 调用较少数量的常见 dao 操作?但是,如果您不介意代码开销,那应该没问题。 -
@Ryan 我的虚拟服务器实例只有 1 个核心(不想在前期花费太多),这意味着除非我将线程池调整为像传统的那样(数十个线程),否则我只能一个线程。我的想法是在框架提供异步功能时使用它。如果我无法弄清楚,同步返回是我的后备。