以功能方式进行多次 API 调用
Making multiple API calls in a functional way
使用 Scala 和 Cats(或者可能是另一个专注于范畴论 and/or 函数式编程的库)以最实用(代数)的方式解决这个问题的最佳方法是什么?
资源
假设我们有以下方法执行 REST API 调用以检索单条信息?
type FutureApiCallResult[A] = Future[Either[String, Option[A]]]
def getNameApiCall(id: Int): FutureApiCallResult[String]
def getAgeApiCall(id: Int): FutureApiCallResult[Int]
def getEmailApiCall(id: Int): FutureApiCallResult[String]
如您所见,它们会产生异步结果。 Either monad 用于 return 在 API 调用期间可能出现的错误,并且 Option 用于 return None 只要 API 找不到资源(这case 不是错误,而是可能的和期望的结果类型)。
以功能方式实现的方法
case class Person(name: String, age: Int, email: String)
def getPerson(id: Int): Future[Option[Person]] = ???
此方法应使用上面定义的三个 API 调用方法来异步组合和 return Person 或 None 如果任何 API 调用失败或API 调用中的任何一个 return None(无法组合整个 Person 实体)
要求
出于性能原因,所有 API 调用必须以并行方式完成
我的猜测
我认为最好的选择是使用 Cats Semigroupal Validated 但是我在尝试处理 Future 时迷路了等等许多嵌套的 Monad :S
谁能告诉我您将如何实现它(即使更改方法签名或主要概念)或指出正确的资源?我在编码方面对猫和代数还很陌生,但我想学习如何处理这种情况,以便我可以在工作中使用它。
这是我遇到的唯一解决方案,但仍然不满意,因为我觉得它可以用更简洁的方式完成
import cats.data.NonEmptyList
import cats.implicits._
import scala.concurrent.Future
case class Person(name: String, age: Int, email: String)
type FutureResult[A] = Future[Either[NonEmptyList[String], Option[A]]]
def getNameApiCall(id: Int): FutureResult[String] = ???
def getAgeApiCall(id: Int): FutureResult[Int] = ???
def getEmailApiCall(id: Int): FutureResult[String] = ???
def getPerson(id: Int): FutureResult[Person] =
(
getNameApiCall(id).map(_.toValidated),
getAgeApiCall(id).map(_.toValidated),
getEmailApiCall(id).map(_.toValidated)
).tupled // combine three futures
.map {
case (nameV, ageV, emailV) =>
(nameV, ageV, emailV).tupled // combine three Validated
.map(_.tupled) // combine three Options
.map(_.map { case (name, age, email) => Person(name, age, email) }) // wrap final result
}.map(_.toEither)
这里的关键要求是它必须并行完成。这意味着使用 monad 的明显解决方案已经过时,因为 monadic bind 是阻塞的(它需要结果以防它必须在其上分支)。所以最好的选择是使用applicative。
我不是 Scala 程序员,所以我不能给你看代码,但我的想法是应用函子可以提升多个参数的函数(一个普通的函子使用 map
).在这里,您将使用 map3
之类的东西来提升 Person
的三参数构造函数以处理三个 FutureResult
。一搜"applicative future in Scala"returns几下。 Either
和 Option
也有应用实例,与 monad 不同的是,应用实例可以很容易地组合在一起。希望这有帮助。
您可以使用 cats.Parallel
类型 class。这使得一些带有 EitherT
的非常简洁的组合器在 运行 并行时会累积错误。所以最简单和最简洁的解决方案是:
type FutureResult[A] = EitherT[Future, NonEmptyList[String], Option[A]]
def getPerson(id: Int): FutureResult[Person] =
(getNameApiCall(id), getAgeApiCall(id), getEmailApiCall(id))
.parMapN((name, age, email) => (name, age, email).mapN(Person))
有关 Parallel
的更多信息,请访问 cats documentation。
编辑:这是没有内部 Option
:
的另一种方式
type FutureResult[A] = EitherT[Future, NonEmptyList[String], A]
def getPerson(id: Int): FutureResult[Person] =
(getNameApiCall(id), getAgeApiCall(id), getEmailApiCall(id))
.parMapN(Person)
就我个人而言,我更喜欢将所有非成功条件折叠成 Future 的失败。这确实简化了错误处理,例如:
val futurePerson = for {
name <- getNameApiCall(id)
age <- getAgeApiCall(id)
email <- getEmailApiCall(id)
} yield Person(name, age, email)
futurePerson.recover {
case e: SomeKindOfError => ???
case e: AnotherKindOfError => ???
}
请注意,这不会 运行 并行请求,为此您需要将 future 的创建移到 for 理解之外,例如:
val futureName = getNameApiCall(id)
val futureAge = getAgeApiCall(id)
val futureEmail = getEmailApiCall(id)
val futurePerson = for {
name <- futureName
age <- futureAge
email <- futureEmail
} yield Person(name, age, email)
使用 Scala 和 Cats(或者可能是另一个专注于范畴论 and/or 函数式编程的库)以最实用(代数)的方式解决这个问题的最佳方法是什么?
资源
假设我们有以下方法执行 REST API 调用以检索单条信息?
type FutureApiCallResult[A] = Future[Either[String, Option[A]]]
def getNameApiCall(id: Int): FutureApiCallResult[String]
def getAgeApiCall(id: Int): FutureApiCallResult[Int]
def getEmailApiCall(id: Int): FutureApiCallResult[String]
如您所见,它们会产生异步结果。 Either monad 用于 return 在 API 调用期间可能出现的错误,并且 Option 用于 return None 只要 API 找不到资源(这case 不是错误,而是可能的和期望的结果类型)。
以功能方式实现的方法
case class Person(name: String, age: Int, email: String)
def getPerson(id: Int): Future[Option[Person]] = ???
此方法应使用上面定义的三个 API 调用方法来异步组合和 return Person 或 None 如果任何 API 调用失败或API 调用中的任何一个 return None(无法组合整个 Person 实体)
要求
出于性能原因,所有 API 调用必须以并行方式完成
我的猜测
我认为最好的选择是使用 Cats Semigroupal Validated 但是我在尝试处理 Future 时迷路了等等许多嵌套的 Monad :S
谁能告诉我您将如何实现它(即使更改方法签名或主要概念)或指出正确的资源?我在编码方面对猫和代数还很陌生,但我想学习如何处理这种情况,以便我可以在工作中使用它。
这是我遇到的唯一解决方案,但仍然不满意,因为我觉得它可以用更简洁的方式完成
import cats.data.NonEmptyList
import cats.implicits._
import scala.concurrent.Future
case class Person(name: String, age: Int, email: String)
type FutureResult[A] = Future[Either[NonEmptyList[String], Option[A]]]
def getNameApiCall(id: Int): FutureResult[String] = ???
def getAgeApiCall(id: Int): FutureResult[Int] = ???
def getEmailApiCall(id: Int): FutureResult[String] = ???
def getPerson(id: Int): FutureResult[Person] =
(
getNameApiCall(id).map(_.toValidated),
getAgeApiCall(id).map(_.toValidated),
getEmailApiCall(id).map(_.toValidated)
).tupled // combine three futures
.map {
case (nameV, ageV, emailV) =>
(nameV, ageV, emailV).tupled // combine three Validated
.map(_.tupled) // combine three Options
.map(_.map { case (name, age, email) => Person(name, age, email) }) // wrap final result
}.map(_.toEither)
这里的关键要求是它必须并行完成。这意味着使用 monad 的明显解决方案已经过时,因为 monadic bind 是阻塞的(它需要结果以防它必须在其上分支)。所以最好的选择是使用applicative。
我不是 Scala 程序员,所以我不能给你看代码,但我的想法是应用函子可以提升多个参数的函数(一个普通的函子使用 map
).在这里,您将使用 map3
之类的东西来提升 Person
的三参数构造函数以处理三个 FutureResult
。一搜"applicative future in Scala"returns几下。 Either
和 Option
也有应用实例,与 monad 不同的是,应用实例可以很容易地组合在一起。希望这有帮助。
您可以使用 cats.Parallel
类型 class。这使得一些带有 EitherT
的非常简洁的组合器在 运行 并行时会累积错误。所以最简单和最简洁的解决方案是:
type FutureResult[A] = EitherT[Future, NonEmptyList[String], Option[A]]
def getPerson(id: Int): FutureResult[Person] =
(getNameApiCall(id), getAgeApiCall(id), getEmailApiCall(id))
.parMapN((name, age, email) => (name, age, email).mapN(Person))
有关 Parallel
的更多信息,请访问 cats documentation。
编辑:这是没有内部 Option
:
type FutureResult[A] = EitherT[Future, NonEmptyList[String], A]
def getPerson(id: Int): FutureResult[Person] =
(getNameApiCall(id), getAgeApiCall(id), getEmailApiCall(id))
.parMapN(Person)
就我个人而言,我更喜欢将所有非成功条件折叠成 Future 的失败。这确实简化了错误处理,例如:
val futurePerson = for {
name <- getNameApiCall(id)
age <- getAgeApiCall(id)
email <- getEmailApiCall(id)
} yield Person(name, age, email)
futurePerson.recover {
case e: SomeKindOfError => ???
case e: AnotherKindOfError => ???
}
请注意,这不会 运行 并行请求,为此您需要将 future 的创建移到 for 理解之外,例如:
val futureName = getNameApiCall(id)
val futureAge = getAgeApiCall(id)
val futureEmail = getEmailApiCall(id)
val futurePerson = for {
name <- futureName
age <- futureAge
email <- futureEmail
} yield Person(name, age, email)