【问题标题】:Future[Either[AppError, Option[User]]] in ScalaScala 中的 Future[Either[AppError, Option[User]]]
【发布时间】:2016-10-27 22:36:09
【问题描述】:

如题所述,使用这样的数据结构有意义吗?让我一一解释:

  • 未来 - 代表异步计算
  • 或者 - 传达已知错误
  • 选项 - 告知该值可能不存在

看到这个我有点害怕。使用这种类型组合是一种好习惯吗?

【问题讨论】:

  • 没有什么问题,是的,你可能有一些更复杂的类型,但它确实代表了你的业务逻辑(我也有类似的东西),如果你对如何更好地感兴趣的话处理这个看看Disjunction(而不是Either)和来自scalaz的EitherT,了解如何将其转换为单个monad,只需处理Option
  • 我在工作中写了一个 web 服务,通常使用这种类型。也许你会觉得这个discussion 很有趣。特别是,我问如何更好地“排序”Future[Either[A, B]]。 @tpolecat 和其他人建议使用 Monad Transformers,以及一个有用的要点示例。

标签: scala functional-programming


【解决方案1】:

我们来看看解空间:

Success(Right(Some(user))) => Everythig OK, got an user
Success(Right(None)) => Everything OK, no user
Success(Left(AppError)) => Something went wrong at app level
Failure(Exception) => Something went wrong

这看起来很有表现力,但是当您尝试将这种嵌套结构与其他调用组合时,事情会变得很糟糕(请参阅Converting blocking code to using scala futures,以获取组合Future[Option[T]] 的示例)

所以关注the principle of the least power, 我们问自己:是否有不那么复杂的替代方案来保留语义? 有人可能会争辩说,如果我们充分利用异常(和异常层次结构)的潜力,Future[User] 就足够了。

让我们检查一下:

Everythig OK, got an user => Success(user)
Everything OK, no user => Failure(UserNotFoundException)  (Application level exception)
Something went wrong at app level => Failure(AppException) (Application level exception)
Something went wrong => Failure(Exception) (System-level exception)

这种方法的唯一限制是 API 的用户需要注意异常,这些异常在界面中没有自我记录。优势在于拥有基于Futures 的API 将允许与其他基于Future 的API 进行富有表现力的一元组合。

【讨论】:

  • 我绝对同意Future[Either[A, B]] 生成的冗长和分配 API。但是,使用异常来描述用户没有找到?这真的是个例外吗?还是这部分是业务需求的常规控制流程?我肯定会谨慎使用异常来描述常见的业务流程。
  • @YuvalItzchakov 当回到应用程序级别时,Exceptions 只是代表不好的类。 :-) 我同意 ADT 是不错的选择。我唯一的一点是从简单开始,并在需要时增加复杂性。
  • 我同意Exception 是一个代表不好的班级。我不知道,我觉得基于它来建模是不对的,不完全理解设计的人可能会得到错误的想法。
【解决方案2】:

一般来说,建议的 API 没有任何问题。它为您提供了所需的灵活性,但需要您编写大量样板来处理返回类型,或者使用 scalaz/cats 和 monadic 转换来提取所有内容。

但是,让我尝试提出一个额外的 API。

让我们定义代数(或抽象数据类型):

// parten me for the terrible name
sealed trait DomainEntity
case class User(id: UserId) extends DomainEntity
case object EmptyUser extends DomainEntity

case class UserId(id: String)

我们没有使用Option[A] 对用户的不存在进行建模,而是使用代数来定义我们的域。

现在,我们可以公开一个Future[Try[DomainEntity]],稍后我们可以将其匹配到 API 生成的不同组合:

findUserById(UserId("id")).map {
  case Success(user: User) => // Do stuff with user
  case Success(EmptyUser) => // We have no user, do something else
  case Failure(e) => // Log exception?
}

【讨论】:

  • @downvoter 我很想知道投反对票的原因。
  • 我也很想知道投反对票的原因。
【解决方案3】:

Future[Either[AppError, Option[User]]] 返回类型之类的东西在制作原型时可能没问题,但一旦完成原型制作,您应该考虑提供更好的可读性和可表达性的选项。

让我们以Future[Either[AppError, Option[User]]] 为例。假设有一个方法具有这种返回类型。

def fetchUser(userId: UUID): Future[Either[AppError, Option[User]]]

现在,您可以选择创建更具表现力的类型层次结构...例如,

// Disclamer :
//     this is just for pointing you out towards a direction and
//     I am sure many can propose a better design hierarchy

trait Model
case class User(id: UUID,....) extends Model

// Fetch Result protocol

sealed trait FetchModelResult

case class FetchModelSuccess(model: Model) extends FetchModelResult

sealed trait FetchModelFailure extends FetchModelResult

case class ModelNotFound extends FetchModelFailure
...
case class FetchModelGenericFailure(ex: Exception) extends FetchModelFailure

// App Result protocol

sealed trait AppResult

case class AppResultSuccess[T](result: T) extends AppResult

sealed trait AppResultFailure extends AppResult

case class AppResultGenericFailure(ex: Exception) extends AppResultFailure

// fetch user problem

def fetchUser(userId: UUID): Future[FetchModelResult] = ???

// Notice that we are not using the generic AppError here
// This is called segregation of problems
// the current problem is fetching the user
// so our design is just to represent what can happen while fetching
// Now whichever method is using this can come-up with an AppError
// or AppResult based on what is gets from here.

def fetchUserApiHandler(userId: UUID): Future[AppResult] =
  fetchUser(userId).map({
    case FetchModelSuccess(model) => .....
    case FetchModelFailure(ex) => ....
  })    

另一种选择是使用来自scalazcats 的一元组合和转换实用程序。

Raúl Raja Martínez 在他的一个演讲中解决了类似的问题,并且解决这些问题的方法很少 - A team's journey over Scala's FP emerging patterns - Run Wild Run Free

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-08-01
    • 2016-11-08
    • 2015-01-05
    • 2016-01-23
    • 2016-04-14
    • 2015-12-28
    相关资源
    最近更新 更多