此多任务操作的功能样式

Functional style for this multi-task Action

我有一个 Play 框架 Action,它需要按此顺序完成 3 件事:-

  1. 从请求中获取两个参数 (A, B)
  2. 如果两个参数都存在则使用 A 从缓存中检索条目
  3. 如果缓存中存在条目,则使用 B
  4. 调用 Web 服务

我目前拥有的:-

// async since we are calling Async APIs like WS

def myAction = Action.async { implicit request =>

    val A = requestParam(request, "a")   // Option[String]
    val B = requestParam(request, "b")

    val futureResponse = for {

        token <- getFromCache(A) recoverWith {
            case e: Exception => Future.failed(new Exception("Some issue with param or cache", e))
        }
        wsResponse <- webServiceCall(B) recoverWith {
            case e: Exception => Future.failed(new Exception("Some issue with web service call", e))
        }

    } yield Ok(wsResponse.body)

     futureResponse recover {
        case e: Exception => Ok(failureBody(e.getMessage))
    }

}

为了简洁起见,我显然省略了一些对我的问题不重要的细节。

我的问题是(作为 Scala 新手)完成此 Action 的实用方法是什么。如果参数不存在,那么我不想执行 for 理解,而是 return 包含错误消息的 Future[Result]。目前我能想到的唯一方法是使用下面的 if 测试,但我不禁怀疑可能有 purer 方法来实现这个。

    val A = requestParam(request, "a")   // Option[String]
    val B = requestParam(request, "b")

    if(A.isEmpty || B.isEmpty) {

        Future { Ok("Params missing") }

    } else {

        val futureResponse = for {

            token <- getFromCache(A) recoverWith {
                case e: Exception => Future.failed(new Exception("Some issue with param or cache", e))
            }
            wsResponse <- webServiceCall(B) recoverWith {
                case e: Exception => Future.failed(new Exception("Some issue with web service call", e))
            }

        } yield Ok(wsResponse.body)

        futureResponse recover {
            case e: Exception => Ok(failureBody(e.getMessage))
    }
}

对于人们如何更优雅地处理我的上述解决方案的任何评论,我将不胜感激。

正如您所注意到的,有很多方法可以处理依赖 FutureOption 的事情,这真的是一个品味问题。如果 if 表达式在给定情况下可以更清楚地传达您的观点,我认为使用 if 表达式并没有什么本质上的错误。那就是说你可以:

使用带有 .getOrElsefor 表达式来确保您的两个参数都得到满足(尽管嵌套的 for 有时看起来不太好)。稍微(不必要地)重写您的逻辑可能如下所示:

import scala.concurrent.Future.{successful => immediate}

def getFromCache(key: String, data: String): Future[String] = ???
def webServiceCall(key: String): Future[WSResponse] = ???

def myAction = Action.async { implicit request =>
  (for {
    a <- request.getQueryString("a")
    b <- request.getQueryString("b")
  } yield (for {
     cachedData <- getFromCache(a)
     r <- webServiceCall(b, cachedData)
   } yield Ok(r.body)) recover {
      case e => InternalServerError(e.getMessage)
   }) getOrElse {
    immediate(BadRequest("params missing"))
  }
}

您还可以在 Play 中选择使用 Form 来确保多个参数存在且有效,因此您最终可能会得到类似以下的内容(再次略微压缩,根据需要展开):

import play.api.data.Forms._
import play.api.data.Form

val form = Form(tuple("a" -> nonEmptyText, "b" -> nonEmptyText))

def myAction2 = Action.async { implicit request =>
  form.bindFromRequest.fold(
    err => immediate(BadRequest("missing params")), { case (a, b) =>
      getFromCache(a).flatMap { cachedData =>
        webServiceCall(b, cachedData).map(r => Ok(r.body))
      } recover {
        case e => InternalServerError(e.getMessage)
      }
    }
  )
}

关于这类事情的一些好帖子是: