正如标题中所提到的,使用这种数据结构是否有意义?让我逐一解释:
看着这个我有点害怕。使用这种类型组合是一种好习惯吗?
答案 0 :(得分:5)
让我们看一下解决方案空间:
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的用户需要了解异常,这在界面中不会自我记录。最重要的是,拥有基于Future
的API将允许表达性的monadic组合与其他基于Future
的API。
答案 1 :(得分:3)
通常,提议的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?
}
答案 2 :(得分:2)
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) => ....
})
另一个选项是使用scalaz
或cats
中的monadic合成和转换实用程序。
RaúlRajaMartínez在他的一个演讲中解决了类似的问题和几种解决方法 - A team's journey over Scala's FP emerging patterns - Run Wild Run Free