[英]How to chain Futures for async database I/O handling?
我最近开始在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)检查用户是否存在手机。
除非我验证两个条件都为假,否则我无法插入新用户。 我实际上可以使f(a)和f(b)并行运行。 如果f(a)评估为true,则并行执行可能是不可取的,否则可能会有利。 创建用户的第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
理解可以减少嵌套时,您是for
。
要解决双重未来问题,请考虑:
existsWithEmail(email).zip(existsWithMobile(mobile)) map {
case (false, false) => // create user
case _ => // already exists
}
如果其中有很多,则可以使用Future.sequence( Seq(future1, future2, ...) )
将期货序列转换为未来序列。
您可能想看一看比DAO更具功能性的DB访问习惯用法,例如Slick或Anorm 。 通常,这些组件将比DAO更好地组成并最终变得更灵活。
附带说明:使用if/else
进行简单的true / false测试要比使用match/case
更为有效,这是首选样式。
我解决了这个问题, for
在scala中for
理解。 我添加了一些隐式类型转换器以帮助进行错误处理。
最初我做了类似的事情,
def someAction = Action.async {
val result =
for {
student <- studentDao.findById(studentId)
if (student.isDefined)
parent <- parentDao.findById(student.get.parentId)
if (parent.isDefined)
address <- addressDao.findById(parent.get.addressId)
if (address.isDefined)
} yield {
// business logic
}
result fallbackTo Future.successful(BadRequest("Something went wrong"))
}
这就是最初构造代码以应对期货之间的依赖关系的方式。 请注意,每个后续的未来取决于先前的未来。 另外,每个findById
都返回Future[Option[T]]
因此if
for
理解该方法,则处理方法返回None
。 如果任何期货的评估结果为“ None
,则我在Future
上使用fallbackTo
方法回BadRequest
结果(如果条件无法理解,则返回失败的期货)上述方法的另一个问题是它将抑制任何遇到了一种异常(甚至是像NPE这样琐碎的异常),而只是回BadRequest
,这是非常糟糕的。
上面的方法能够对付期权的未来并处理失败的案例,尽管无法准确地理解理解中的哪个期货失败了。 为了克服此限制,我使用了隐式类型转换器。
object FutureUtils {
class FutureProcessingException(msg: String) extends Exception(msg)
class MissingOptionValueException(msg: String) extends FutureProcessingException(msg)
protected final class OptionFutureToOptionValueFuture[T](f: Future[Option[T]]) {
def whenUndefined(error: String)(implicit context: ExecutionContext): Future[T] = {
f.map { value =>
if (value.isDefined) value.get else throw new MissingOptionValueException(error)
}
}
}
import scala.language.implicitConversions
implicit def optionFutureToValueFutureConverter[T](f: Future[Option[T]]) = new OptionFutureToOptionValueFuture(f)
}
上面的隐式转换使我对链接多个期货的理解写成可读性。
import FutureUtils._
def someAction = Action.async {
val result =
for {
student <- studentDao.findById(studentId) whenUndefined "Invalid student id"
parent <- parentDao.findById(student.get.parentId) whenUndefined "Invalid parent id"
address <- addressDao.findById(parent.get.addressId) whenUndefined "Invalid address id"
} yield {
// business logic
}
result.recover {
case fpe: FutureProcessingException => BadRequest(fpe.getMessage)
case t: Throwable => InternalServerError
}
}
上面的方法确保了所有由于缺少Option值而导致的异常都将作为BadRequest
处理,并带有有关确切失败原因的特定消息。 所有其他故障都被视为InternalServerError
。 您可以使用堆栈跟踪记录确切的异常,以帮助调试。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.