タイトルのとおり、このようなデータ構造を使用するのは意味がありますか?私は一つずつ説明しましょう:将来[Scalaの[AppError、Option [User]]]のいずれか
- フューチャー - どちらか非同期計算
- を表現するために - 既知のエラーに
- オプションの通信に - 値が
存在しないかもしれないことを通信するために私はこれを見て少し怖い。このようなタイプの組み合わせを使用するのがよい方法ですか?
タイトルのとおり、このようなデータ構造を使用するのは意味がありますか?私は一つずつ説明しましょう:将来[Scalaの[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
からモナド組成と変換ユーティリティを使用することで、例えば...より表現型階層を作成することを選択することができます。
ラウル・ラジャ・マルティネスは、類似した問題と彼のプレゼンテーションのいずれかでこれらに対抗する方法をいくつか取り上げています - A team's journey over Scala's FP emerging patterns - Run Wild Run Free
はのは、解空間を見てみましょう:
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
これは非常に表情豊かに見えますが、 Future[Option[T]]
の例については、他の呼び出し(Converting blocking code to using scala futuresを参照)でそのようなネストされた構造を作成しようとすると、物事は醜いものになります。
したがって、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を有することは、他のFuture
ベースのAPIで表現的なモナディック組成を可能にすることである。
'Future [A、B]]で生成された冗長と割り当てAPIに関しては、私は間違いなく同意します。ただし、ユーザーを記述するための例外を使用しているのは見つかりませんか?それは本当に例外的なケース*ですか?または、ビジネス要件の定期的な制御フローのこの部分ですか?私は間違いなく、一般的なビジネスフローを記述する手段として例外を使用することに注意しています。 –
@YuvalItzchakovアプリケーションレベルに戻ったとき、 'Exception'は悪い担当者のクラスです。 :-)私はADTが良い選択であることに同意します。私の唯一のポイントは、シンプルなものから始まり、必要に応じて複雑さを加えることです。 – maasg
「例外」は悪い担当者がいるクラスであることに同意します。私はそれに基づいて何かをモデル化することは私には正しいとは思わないが、デザインを完全に理解していない人は間違った考えを得るかもしれない。 –
一般に、提案された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]
でユーザーの非存在をモデル化する、私たちは私たちのドメインを定義するために私たちの代数を使用しています。
今、私たちは、後でAPIによって生成されたさまざまな組み合わせのために一致させることができFuture[Try[DomainEntity]]
、公開することができます。
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私はdownvoteの理由を知りたいです。 –
私はダウン投票の理由も知りたいのです。 – pedrofurla
をそれと間違って何もありません、はい、あなたは、より複雑な型のビットを持っていますが、それもあなたがこれをより良く扱う方法に興味があるならば、これを変える方法についてはscalazからの 'EitherT'と' EitherT'を見てください。単一のモナドとして、 'Option'に対処する必要があるだけです。 –
このタイプを一般的に使用している職場でWebサービスを作成しました。おそらくあなたはこのディスカッション(https://www.reddit.com/r/scala/comments/3r5ii6/on_handling_futureeithera_b_sequencing/)に興味があるでしょう。特に、私は、「未来[A、B]どちらか」をよりよく「順序づけ」する方法を尋ねました。 @tpolecatなどはMonadトランスフォーマーの使用をお勧めします。 –