2013-09-16 88 views
4

我們使用Twitter期貨(作爲Finagle堆棧的一部分),我不喜歡使用(業務)異常來控制應用程序流的概念, t出現在方法簽名中。結合期貨(Twitter)和斯卡拉

所以我有想法使用未來[或者[A,B]]作爲替代品。

但我有一些問題在期貨上使用推導這個概念:

例如我們有一個庫方法:

def getUserCredentialsByNickname(nickname: String): Future[Either[EntityNotFound, UserCredentials]] 

和處理方法,其在使用該回購和做一些其他檢查,並創建了一個令牌

def process(request: LoginRequest): Future[Either[Failure, Login]] = { 
     for { 
     credentialsEither <- userRepository.getUserCredentialsByNickname(request.username) 
     ...several other calls/checks which should 'interrupt' this for comprehension 
     token <- determineToken(credentials) 
} yield token 

的調用將在getUserCredentialsByNickname後理解(.. )只應該在這個調用返回一個Right [UserCredentials]的時候被執行,而且每個返回的Either的詳細錯誤信息都應該從處理程序返回。

+0

也許這將是更好的使用已經有欺騙期貨集成了'Try'數據結構:HTTP://嘰嘰喳喳.github.io/util/util-core/target/site/doc/main/api/com/twitter/util/Try.html –

+0

我看過也試過,但其缺點是事實(就我而言瞭解它),它沒有在方法的簽名中指定確切的錯誤情況,或者在使用它時的類型。通過上面的簽名'未來[EntityNotFound,UserCredentials]'我可以立即看到什麼樣的故障類型可以被返回。當我嘗試使用時,我必須查看處理Try的代碼的實現。 – longliveenduro

回答

4

所以現在我試着使用Scalaz Either(這是一個正確的偏見要麼與中性Scala相比)和Monad Transformer EitherT,它似乎確實是我想要的。感謝Huw,特別是Lars Hupel向我暗示了正確的方向。

這裏正在爲Twitter的期貨和Scalaz要麼和EitherT樣本:

import com.twitter.util.{Await, Future} 
import scalaz.{Monad, Functor, EitherT, \/} 
import scalaz.syntax.ToIdOps 

object EitherTest extends App with ToIdOps{ 

    // make Twitter futures work with EitherT 
    implicit val FutureFunctor = new Functor[Future] { 
    def map[A, B](a: Future[A])(f: A => B): Future[B] = a map f 
    } 
    implicit val FutureMonad = new Monad[Future] { 
    def point[A](a: => A): Future[A] = Future(a) 
    def bind[A, B](fa: Future[A])(f: (A) => Future[B]): Future[B] = fa flatMap f 
    } 

    // The example begins here: 

    case class InvalidInfo(error: String) 
    case class Response(msg: String) 


    class ComponentA { 
    def foo(fail: Boolean): Future[\/[InvalidInfo, Response]] = { 
     if(fail) Future(InvalidInfo("Error A").left) else Future(Response("ComponentA Success").right) 
    } 
    } 
    class ComponentB { 
    def bar(fail: Boolean): Future[\/[InvalidInfo, Response]] = { 
     if(fail) Future(InvalidInfo("Error B").left) else Future(Response("ComponentB Success").right) 
    } 
    } 

    val a = new ComponentA 
    val b = new ComponentB 

    val result = for { 
    resultA <- EitherT(a.foo(false)) 
    resultB <- EitherT(b.bar(false)) 
    } yield (resultA, resultB) 

    println(Await.result(result.run)) 
} 
2

你可以通過隱含加入,而不必自己每次來配合它,處理Either的方法,擴展以後的類:

implicit class EitherHandlingFuture[Exception, Value](future: Future[Either[Exception, Value]]) { 
    def mp[Return](fn: Value => Return) = { 
    future.map { eth: Either[Exception, Value] => 
     eth match { 
     case Left(ex: Exception) => { print("logging the exception") /* handle or rethrow */ } 
     case Right(res: Value) => fn(res) 
     } 
    } 
    } 
} 

那麼,這將是可能的:

def someComputation: Future[Either[Exception, Int]] = Future.value(Right(3)) 

someComputation mp { res: Int => 
    println(res) 
} 

請注意,上面的代碼片段不支持for解析,因爲要支持它們,有必要完全實現map/flatMap。爲此,你可能想要子類Future

+1

我認爲使用/基於Scalaz的EitherT/ValidationT的解決方案比運行循環子類更好。 – Hugh

+0

@胡:我根本不認識斯卡拉茲。你有我的用例的例子嗎? – longliveenduro

+1

斯卡拉斯適合使用的用例有一定的子集。在開發人員的生產力和可讀性方面,這也是一筆巨大的投資。它在一定程度上解決了這個問題並不意味着它是一個明確的解決方案。 –