简体   繁体   English

未来[Scala中的[AppError,Option [User]]]

[英]Future[Either[AppError, Option[User]]] in Scala

As mentioned in the title, does it make sense to use such data structure? 正如标题中所提到的,使用这种数据结构是否有意义? Let me explain one by one: 让我逐一解释:

  • Future - to represent async computation 未来 - 表示异步计算
  • Either - to communicate known errors 要么 - 传达已知错误
  • Option - to communicate that the value may not be present 选项 - 表示可能不存在该值

I am a little bit scared when looking at this. 看着这个我有点害怕。 Is it a good practice to use such type combination? 使用这种类型的组合是一个好习惯吗?

Let's have a look at the solution space: 我们来看一下解决方案空间:

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

This looks very expressive, but things get ugly fast when you try to compose such nested structure with other calls (see Converting blocking code to using scala futures ) for an example of composing Future[Option[T]] ) 这看起来非常具有表现力,但是当你尝试用其他调用组合这样的嵌套结构时(参见将阻塞代码转换为使用scala期货 )来组成Future[Option[T]] )的例子会变得很难看

So following the principle of the least power , we ask ourselves: Are there less complex alternatives that preserve the semantics? 因此,遵循最小权力的原则 ,我们会问自己:是否存在保留语义的不太复杂的替代方案? One could argue that Future[User] could be sufficient if we make use of the full potential of exception (and exception hierarchies). 有人可能会说,如果我们充分利用异常(和异常层次结构)的全部潜力, Future[User]就足够了。

Let's check: 让我们检查:

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)

The only limitation of this approach is that the users of the API will need to be aware of the Exceptions, which is not self-documented in the interface. 此方法的唯一限制是API的用户需要了解异常,这在界面中不会自我记录。 The upper-hand is that having an API based on Future s will allow expressive monadic compositions with other Future -based APIs. 最重要的是拥有基于Future s的API将允许表达式monadic组合与其他基于Future的API。

Generally, there is nothing wrong with the proposed API. 通常,提议的API没有任何问题。 It gives you exactly the flexibility you need, but requires you to either write a decent amount of boilerplate to deal with the return type, or use scalaz/cats and monadic transformations to extract everything. 它为您提供了所需的灵活性,但要求您编写大量的样板来处理返回类型,或者使用scalaz / cats和monadic转换来提取所有内容。

But, Let me try and propose an additional API. 但是,让我尝试提出一个额外的API。

Let's define our algebra (or abstract data types): 让我们定义我们的代数(或抽象数据类型):

// 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)

Instead of modeling the non-existence of a user with an Option[A] , we use our algebra to define our domain. 我们使用代数来定义我们的域,而不是使用Option[A]对用户的不存在进行建模。

Now, we can expose a Future[Try[DomainEntity]] , which we can later match for different combinations generated by the API: 现在,我们可以公开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?
}

Things like Future[Either[AppError, Option[User]]] return type can be ok at the time of prototyping things but once you are done with prototyping you should think of options which offer better readability and expressibility. 诸如Future[Either[AppError, Option[User]]]返回类型之类的东西在原型设计时可以正常,但是一旦完成原型设计,您应该考虑提供更好的可读性和可表达性的选项。

Lets take this Future[Either[AppError, Option[User]]] as an example. 让我们以Future[Either[AppError, Option[User]]]为例。 Lets say there is a method which has this return type. 让我们说有一种方法具有这种返回类型。

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

Now, you can either choose to create a more expressive type hierarchy... for example, 现在,您可以选择创建更具表现力的类型层次结构...例如,

// 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) => ....
  })    

The other option will be to use monadic composition and transformation utilities from scalaz or cats . 另一种选择将是使用从一元组成,改造公用事业scalazcats

Raúl Raja Martínez has addressed similar problems and few ways to counter these in one of his presentations - A team's journey over Scala's FP emerging patterns - Run Wild Run Free RaúlRajaMartínez在他的一个演讲中解决了类似的问题以及解决这些问题的几种方法 - 一个团队的Scala FP新兴模式之旅 - Run Wild Run Free

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM