Created
August 8, 2012 22:32
-
-
Save eboto/3299446 to your computer and use it in GitHub Desktop.
How to use Scala's Either[A, B] sanely for error handling
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// Demonstration on how to use Scala's Either[A, B] type to include error cases in function return values | |
// and eventually transform those domain errors into more useful end types, like Redirects. | |
// | |
// This demonstration uses the example of ordering a product, where ordering can fail for a variety | |
// of reasons including failure in our payment provider (Stripe) or failure due to insufficient | |
// product inventory. | |
// | |
object Main { | |
def main(args: Array[String]) { | |
// Attempt a purchase, which returns an Either[PurchaseFailed, Order] | |
val errorOrOrder = performPurchase | |
// Fold errorOrOrder, which could have either been a failure or a success, into a single | |
// type. Here we fold it into a String, but in the case of a Play app, we would fold it | |
// into a Redirect type. | |
// | |
// fold takes two parameters: | |
// (1) a function that takes the error type and returns something, | |
// (2) a function that takes the success type and returns something. | |
// | |
// fold returns the closest common ancestor type between the return value | |
// of the first and second argument functions. | |
// | |
// In our case the error type is PurchaseFailed, the success type is Order. | |
val result = errorOrOrder.fold( | |
(error) => error match { | |
case stripeError: PurchaseFailedStripeError => | |
"A redirect to the stripe error page" | |
case insufficientInventory: PurchaseFailedInsufficientInventory => | |
"A redirect to the insufficient inventory page" | |
}, | |
(successfulOrder) => | |
"A redirect to the order confirmation page" | |
) | |
println("Result was: " + result) | |
println("") | |
} | |
// The function that actually performs the purchase | |
def performPurchase: Either[PurchaseFailed, Order] = { | |
// Create fake Celebrity Products that we're buying | |
val celeb = new Celebrity | |
val product = new Product | |
// Fake code that models the different error or success cases | |
val oopsStripeError = false | |
val oopsInsufficientInventory = true | |
if (oopsStripeError) { | |
Left(PurchaseFailedStripeError(celeb, product)) | |
} | |
else if (oopsInsufficientInventory) { | |
Left(PurchaseFailedInsufficientInventory(celeb, product)) | |
} | |
else { | |
Right(new Order(1L)) | |
} | |
} | |
} | |
// Failure base type | |
sealed trait PurchaseFailed | |
// Our two failure cases for if the payment vendor failed or there was insufficient inventory | |
case class PurchaseFailedStripeError(celebrity: Celebrity, product: Product) extends PurchaseFailed | |
case class PurchaseFailedInsufficientInventory(celebrity: Celebrity, product: Product) extends PurchaseFailed | |
// Faux domain model objects | |
class Celebrity | |
class Product | |
class Order(id: Long) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment