Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Picking the right exception handling in scala

I'm new to Scala, being slightly confused at the various ways to handle exceptions and looking for best-practice advice on the topic. I'm writing a simple method to retrieve a Customer using an existing blocking SDK. The possible outcomes are:

  1. Customer is found
  2. Customer is not found (comes back as a NotFoundException from the SDK)
  3. Error while talking to remote server (SDK throws some other exception)

So I want my method to have a return type Future[Option[Customer]], and return for each case above:

  1. Successful Future: Some(customer)
  2. Successful Future: None
  3. Failed Future

Here is what I wrote using try/catch:

private def findCustomer(userId: Long): Future[Option[Customer]] = future {
  try {
    Some(gateway.find(userId))
  } catch {
    case _: NotFoundException => None
  }
}

This works fine and seems clean to me, but doesn't seem to be really the "Scala way" to do it - I've been told to avoid using try/catch. So I've been looking for a way to rewrite it using a Try instead.

Here are 2 variants that (I think) behave exactly the same, using a Try.

Variant A:

private def findCustomer(userId: Long): Future[Option[Customer]] = future {
  Try(
    Some(gateway.find(userId))
  ).recover {
    case _: NotFoundException => None
  }.get
}

Variant B:

private def findCustomer(userId: Long): Future[Option[Customer]] = future {
  Try(
    Some(gateway.find(userId))
  ).recover {
    case _: NotFoundException => None
  }
} flatMap {
  case Success(s) => Future.successful(s)
  case Failure(f) => Future.failed(f)
}

I'm not a huge fan of A (even though it's more concise than B) since the .get seems a bit treacherous. B is definitely the most explicit but mapping the Try cases to corresponding Future outcomes seems boring.

How would an experienced Scala programmer write this?

like image 278
Jules Olléon Avatar asked Feb 10 '23 15:02

Jules Olléon


1 Answers

I think your initial version using try/catch is perfectly fine, because it's a wrapper around an existing SDK.

Alternatively, you can use the recover method on Future:

def findCustomer(userId: Long): Future[Option[Customer]] =
  Future(Option(gateway.find(userId))).recover {
    case e: NotFoundException => None
  }
like image 182
Ionuț G. Stan Avatar answered Feb 16 '23 02:02

Ionuț G. Stan