2013-05-01 14 views
10

Mi piacerebbe sapere quale dovrebbe essere la firma dei miei metodi in modo da gestire elegantemente diversi tipi di guasti .Prova [Risultato], IO [Risultato], O [Errore, Risultato], che dovrei usare alla fine

Questa domanda è in qualche modo il riassunto di molte domande che avevo già sulla gestione degli errori in Scala. Potete trovare alcune domande qui:


Per il momento, ho capito quanto segue:

  • entrambi possono essere utilizzati come un risultato wrapper per una chiamata di metodo che potrebbe non riuscire
  • Provare è un diritto biaised Sia in cui il fallimento è un'eccezione non fatale
  • IO (scalaz) aiuta a costruire metodi puri che gestiscono le operazioni di IO
  • Tutti e 3 sono facilmente utilizzabile in un di comprensione
  • Tutti e 3 non sono facilmente miscelabile in una di comprensione a causa dei metodi di flatMap incompatibili
  • in langages funzionali che di solito non generare eccezioni a meno che non sono fatali
  • ci dovrebbe generare eccezioni per davvero eccezionale condizioni. Credo che questo è l'approccio di Prova
  • Creazione Throwables ha un costo delle prestazioni per la JVM e non è destinato ad essere utilizzato per controle flusso di business

strato Repository

Ora per favore considera che ho un UserRepository. Lo UserRepository memorizza gli utenti e definisce un metodo findById. I seguenti guasti potrebbe accadere:

  • Un guasto fatale (OutOfMemoryError)
  • fallimento Un IO perché il database non è accessibile/leggibile

Inoltre, l'utente poteva mancare, portando ad un Option[User] risultato

Utilizzando un'implementazione JDBC del repository, SQL, eccezioni non fatali (violazione del vincolo o altro) possono essere generate in modo che possa essere opportuno utilizzare Try.

Dato che si tratta di operazioni IO, anche la monade IO ha senso se vogliamo funzioni pure.

Così il tipo di risultato potrebbe essere:

  • Try[Option[User]]
  • IO[Option[User]]
  • qualcos'altro?

strato Servizio

Ora diamo introdurre un livello di business, UserService, che fornisce un metodo che utilizza il updateUserName(id,newUserName) definito in precedenza findById del repository.

I seguenti errori possono accadere:

  • Tutti i guasti repository propagate al livello di servizio
  • errore di azienda: non è possibile aggiornare il nome utente di un utente che non esiste
  • errore di business: il nuovo nome utente è troppo breve

Poi il tipo di risultato potrebbe essere:

  • Try[Either[BusinessError,User]]
  • IO[Either[BusinessError,User]]
  • qualcos'altro?

BusinessError qui non è un Throwable perché non è un fallimento eccezionale.


Usando per-comprensioni

vorrei continuare a utilizzare per-comprensioni di combinare chiamate di metodo.

Non possiamo facilmente mescolare diverse monadi su una comprensione preliminare, quindi suppongo che dovrei avere una sorta di tipo uniforme di ritorno per tutte le mie operazioni, giusto?

Mi chiedo solo come riesci, nel tuo mondo reale applicazioni Scala, a continuare a usare le incomprensioni quando possono accadere diversi tipi di errori.

Per ora, per la comprensione funziona bene per me, utilizzando servizi e repository che restituiscono tutti Either[Error,Result] ma tutti i diversi tipi di guasti si fondono insieme e diventa un po 'hacky per gestire questi errori.

Definite conversioni implicite tra diversi tipi di monadi per poter utilizzare le incomprensioni?

Definite le vostre monadi per gestire i guasti?

A proposito, forse userò un IO driver asincrono al più presto. Quindi credo che il mio tipo di ritorno potrebbe essere ancora più complicato: IO[Future[Either[BusinessError,User]]]


Qualsiasi consiglio è benvenuto perché io non so davvero cosa usare, mentre la mia applicazione non è fantasia: si tratta solo di un'API in cui Dovrei essere in grado di fare una distinzione tra errori di business che possono essere mostrati al lato client e errori tecnici. Cerco di trovare una soluzione elegante e pura.

+0

Potresti postare un po 'di codice come hai finito per scrivere questo? Sono molto interessato perché ho in mente un modello simile. Le mie capacità di scalaz non sono quelle che vorrei che fossero ... Ho difficoltà a dare un senso a queste lezioni. – costa

risposta

11

Questo è il trasformatore monade EitherT di Scalaz. Una pila di IO[Either[E, A]] equivale a EitherT[IO, E, A], tranne per il fatto che il primo deve essere gestito come più monadi in sequenza, mentre il secondo è automaticamente una monade singola che aggiunge le funzionalità alla monade di base IO. È inoltre possibile utilizzare EitherT[Future, E, A] per aggiungere la gestione degli errori non eccezionale alle operazioni asincrone.

I trasformatori Monad in generale sono la risposta alla necessità di miscelare più monadi in una singola operazione for -comprehension e/o monadic.


EDIT:

io assumerà si utilizza Scalaz versione 7.0.0.

Per poter utilizzare il trasformatore EitherT monade, in cima alla IO Monade, è necessario prima di importare le parti pertinenti Scalaz:

import scalaz._, scalaz.effect._ 

È inoltre necessario definire i tipi di errore: RepositoryError, BusinessError, ecc. Funziona come al solito. Devi solo assicurarti di poter convertire, ad esempio, qualsiasi RepositoryError in un BusinessError e quindi abbinare lo schema per recuperare il tipo esatto di errore.

Poi le firme dei tuoi metodi diventano:

def findById(id: ID): EitherT[IO, RepositoryError, User] 
def updateUserName(id: ID, newUserName: String): EitherT[IO, BusinessError, User] 

All'interno di ciascuno dei tuoi metodi, è possibile utilizzare lo stack-based monade EitherT -e- IO come un unico, unificato Monade, disponibile in for -comprehensions come di solito. EitherT si occuperà di eseguire il threading della monade di base (in questo caso IO) attraverso l'intero computo, gestendo anche gli errori nel modo in cui lo fa (eccetto già il diritto di polarizzazione per impostazione predefinita, in modo da non dover gestire costantemente tutte le solito .right junk). Quando si desidera eseguire un'operazione IO, tutto ciò che si deve fare è sollevarlo nello stack monad combinato usando il metodo di istanza liftIO su IO.

Come nota a margine, quando si lavora in questo modo, le funzioni nell'oggetto compagno EitherT possono essere molto utili.

+1

Grazie, questo sembra essere esattamente ciò di cui ho bisogno. Sfortunatamente non ho ancora le capacità di Scalaz per capire come funziona davvero :(Puoi fornire un semplice esempio di come usarli, usando le firme dei metodi che ho sopra? –

+0

@SebastienLorber Ho modificato la mia risposta per provare a spiegare meglio –

+1

@SebastienLorber una punta di Scalaz.È una buona idea provare a ri-implementare alcune cose da solo per capire meglio come funzionano. In questo caso, l'implementazione dell'istanza Monad per 'EitherT' è un ottimo esercizio. – Eric

Problemi correlati