加入收藏 | 设为首页 | 会员中心 | 我要投稿 李大同 (https://www.lidatong.com.cn/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 综合聚焦 > 服务器 > 安全 > 正文

Scala-way处理for-comprehensions的条件?

发布时间:2020-12-16 09:28:45 所属栏目:安全 来源:网络整理
导读:我正在尝试创建一个整洁的结构,以便理解基于未来的业务逻辑.这是一个示例,其中包含基于异常处理的工作示例: (for { // find the user by id,findUser(id) returns Future[Option[User]] userOpt - userDao.findUser(userId) _ = if (!userOpt.isDefined) th
我正在尝试创建一个整洁的结构,以便理解基于未来的业务逻辑.这是一个示例,其中包含基于异常处理的工作示例:

(for {
  // find the user by id,findUser(id) returns Future[Option[User]]
  userOpt <- userDao.findUser(userId)        
  _ = if (!userOpt.isDefined) throw new EntityNotFoundException(classOf[User],userId)

  user = userOpt.get       

  // authenticate it,authenticate(user) returns Future[AuthResult]
  authResult <- userDao.authenticate(user)   
  _ = if (!authResult.ok) throw new AuthFailedException(userId)

  // find the good owned by the user,findGood(id) returns Future[Option[Good]]
  goodOpt <- goodDao.findGood(goodId)        
  _ = if (!good.isDefined) throw new EntityNotFoundException(classOf[Good],goodId)

  good = goodOpt.get        

  // check ownership for the user,checkOwnership(user,good) returns Future[Boolean]
  ownership <- goodDao.checkOwnership(user,good)
  if (!ownership) throw new OwnershipException(user,good)

  _ <- goodDao.remove(good) 
} yield {
  renderJson(Map(
    "success" -> true
  ))
})
.recover {
  case ex: EntityNotFoundException =>
    /// ... handle error cases ...
    renderJson(Map(
        "success" -> false,"error" -> "Your blahblahblah was not found in our database"
    ))
  case ex: AuthFailedException =>
    /// ... handle error cases ...
  case ex: OwnershipException =>
    /// ... handle error cases ...
}

但是,这可能被视为处理事物的非功能性或非Scala方式.有一个更好的方法吗?

请注意,这些错误来自不同的来源 – 有些属于业务级别(‘检查所有权’),有些属于控制器级别(‘授权’),有些属于数据库级别(‘未找到实体’).因此,从单个常见错误类型派生它们时的方法可能不起作用.

解决方法

主要的挑战是,for-comprehensions一次只能在一个monad上工作,在这种情况下它是Future monad,并且短路未来调用序列的唯一方法是将来失败.这是有效的,因为for-comprehension中的后续调用只是map和flatmap调用,而失败的Future上的map / flatmap的行为是返回该未来并且不执行提供的主体(即被调用的函数).

您要实现的目标是基于某些条件的工作流程的短暂启动,而不是未来的失败.这可以通过将结果包装在另一个容器中来完成,让我们称之为Result [A],它为理解提供了一种Future [Result [A]].结果将包含结果值,或者是终止结果.面临的挑战是如何:

>提供后续函数调用先前非终止结果包含的值
>如果结果正在终止,则阻止评估后续函数调用

map / flatmap看起来像是做这些类型的合成的候选者,除了我们必须手动调用它们,因为for-comprehension可以评估的唯一map / flatmap是导致Future [Result [A]]的地图/ flatmap.

结果可以定义为:

trait Result[+A] {

  // the intermediate Result
  def value: A

  // convert this result into a final result based on another result
  def given[B](other: Result[B]): Result[A] = other match {
    case x: Terminator => x
    case v => this
  }

  // replace the value of this result with the provided one
  def apply[B](v: B): Result[B]

  // replace the current result with one based on function call
  def flatMap[A2 >: A,B](f: A2 => Future[Result[B]]): Future[Result[B]]

  // create a new result using the value of both
  def combine[B](other: Result[B]): Result[(A,B)] = other match {
    case x: Terminator => x
    case b => Successful((value,b.value))
  }
}

对于每个调用,该操作实际上是一个潜在的操作,因为调用它或使用终止结果,将简单地保持终止结果.请注意,Terminator是Result [Nothing],因为它永远不会包含值,并且任何Result [A]都可以是Result [Nothing].

终止结果定义为:

sealed trait Terminator extends Result[Nothing] {
  val value = throw new IllegalStateException()

  // The terminator will always short-circuit and return itself as
  // the success rather than execute the provided block,thus
  // propagating the terminating result
  def flatMap[A2 >: Nothing,B](f: A2 => Future[Result[B]]): Future[Result[B]] =
    Future.successful(this)

  // if we apply just a value to a Terminator the result is always the Terminator
  def apply[B](v: B): Result[B] = this

  // this apply is a convenience function for returning this terminator
  // or a successful value if the input has some value
  def apply[A](opt: Option[A]) = opt match {
    case None => this
    case Some(v) => Successful[A](v)
  }

  // this apply is a convenience function for returning this terminator or
  // a UnitResult
  def apply(bool: Boolean): Result[Unit] = if (bool) UnitResult else this
}

当我们已经满足终止条件时,终止结果可以使对需要值[A]的函数的调用短路.

非终止结果定义为:

trait SuccessfulResult[+A] extends Result[A] {

  def apply[B](v: B): Result[B] = Successful(v)

  def flatMap[A2 >: A,B](f: A2 => Future[Result[B]]): Future[Result[B]] = f(value)
}

case class Successful[+A](value: A) extends SuccessfulResult[A]

case object UnitResult extends SuccessfulResult[Unit] {
  val value = {}
}

非终止结果使得可以向函数提供包含的值[A].为了更好的衡量,我还为纯粹副作用的函数预定了一个UnitResult,比如goodDao.removeGood.

现在让我们来定义你的好但终止的条件:

case object UserNotFound extends Terminator

case object NotAuthenticated extends Terminator

case object GoodNotFound extends Terminator

case object NoOwnership extends Terminator

现在我们有了创建您正在寻找的工作流程的工具.每个for comprehention想要一个函数,它返回右侧的Future [Result [A]],在左侧产生Result [A].结果[A]上的flatMap可以调用(或短路)需要[A]作为输入的函数,然后我们可以将其结果映射到新结果:

def renderJson(data: Map[Any,Any]): JsResult = ???
def renderError(message: String): JsResult = ???

val resultFuture = for {

  // apply UserNotFound to the Option to conver it into Result[User] or UserNotFound
  userResult <- userDao.findUser(userId).map(UserNotFound(_))

  // apply NotAuthenticated to AuthResult.ok to create a UnitResult or NotAuthenticated
  authResult <- userResult.flatMap(user => userDao.authenticate(user).map(x => NotAuthenticated(x.ok)))

  goodResult <- authResult.flatMap(_ => goodDao.findGood(goodId).map(GoodNotFound(_)))

  // combine user and good,so we can feed it into checkOwnership
  comboResult = userResult.combine(goodResult)

  ownershipResult <- goodResult.flatMap { case (user,good) => goodDao.checkOwnership(user,good).map(NoOwnership(_))}

  // in order to call removeGood with a good value,we take the original
  // good result and potentially convert it to a Terminator based on
  // ownershipResult via .given
  _ <- goodResult.given(ownershipResult).flatMap(good => goodDao.removeGood(good).map(x => UnitResult))
} yield {

  // ownership was the last result we cared about,so we apply the output
  // to it to create a Future[Result[JsResult]] or some Terminator
  ownershipResult(renderJson(Map(
    "success" -> true
  )))
}

// now we can map Result into its value or some other value based on the Terminator
val jsFuture = resultFuture.map {
  case UserNotFound => renderError("User not found")
  case NotAuthenticated => renderError("User not authenticated")
  case GoodNotFound => renderError("Good not found")
  case NoOwnership => renderError("No ownership")
  case x => x.value
}

我知道这是一个很多设置,但至少Result类型可以用于任何具有终止条件的Future for-comprehension.

(编辑:李大同)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

    推荐文章
      热点阅读