繁体   English   中英

如何链接期货以进行异步数据库I / O处理?

[英]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访问习惯用法,例如SlickAnorm 通常,这些组件将比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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM