First of all, I don’t want to raise a topic “Exceptions versus Error Codes”. But if you need to return a result code and you use Scala, I have an elegant solution for you to do so.

We have a simple contract:

def processRequest(userId: UUID, requestId: UUID): BusinessResult

Where BusinessResult is plain Java enumeration:

public enum BusinessResult
{
    Ok, UserNotFound, RequestNotFound, NotOwner
}

Java-style

The old-school implementation of this method would be:

def processRequestOld(userId: UUID, requestId: UUID): BusinessResult = {
  val userOpt = getUser(userId)
  if (userOpt.isEmpty) {
    return BusinessResult.UserNotFound
  }

  val requestOpt = getRequestById(requestId)
  if (requestOpt.isEmpty) {
    return BusinessResult.RequestNotFound
  }

  if (checkAccess(requestOpt.get, userOpt.get).isFailure) {
    return BusinessResult.NotOwner
  }

  BusinessResult.Ok
}

Pros:

  • Very clear to a reader (even for unfamiliar with Scala)

Cons:

  • In Scala World it’s uncommon to use return keyword
  • Also using of Option.get function is uncommon and considered as a bad practice mostly

Use exceptions internally

Another solution is to use exceptions:

def processRequestWithException(userId: UUID, requestId: UUID): BusinessResult = {
  case class BusinessException(result: BusinessResult) extends RuntimeException

  try {
    val user = getUser(userId).getOrElse(throw new BusinessException(BusinessResult.UserNotFound))
    val request = getRequestById(requestId).getOrElse(throw new BusinessException(BusinessResult.RequestNotFound))
    checkAccess(request, user).toOption.getOrElse(throw new BusinessException(BusinessResult.NotOwner))
    BusinessResult.Ok
  } catch {
    case be: BusinessException => be.result
  }
}

Pros:

  • More concise;
  • Not using Option.get function;
  • You may extract the code without try..catch and use it for a transition to

exception-based error control (if you’re going to that transition).

Cons:

  • Usage of exceptions without actual need.

Scala collections

I tried to realize, how to use collections API for such tasks, i.e. using Option.fold method:

def processRequestCollections(userId: UUID, requestId: UUID): BusinessResult = {
  getUser(userId).fold(BusinessResult.UserNotFound)(user => {
    getRequestById(requestId).fold(BusinessResult.RequestNotFound)(request => {
      checkAccess(request, user).toOption.fold(BusinessResult.NotOwner)(_ => BusinessResult.Ok)
    })
  })
}

It looks concise, but in this approach you will increase indent for each result code.

Either FTW

The common use case for Either is a replacement for Option where None will contain some meaningful value (by convention it’s Left).

We will use for comprehensions on RightProjection of our Either. If we have Left (which means an error) then this value will be returned, otherwise there will be a call to flatMap function with the next RightProjection etc.

def processRequestEither(userId: UUID, requestId: UUID): BusinessResult = {
  val result: Either[BusinessResult, BusinessResult] = for {
    user <- either(getUser(userId), BusinessResult.UserNotFound)
    request <- either(getRequestById(requestId), BusinessResult.RequestNotFound)
    _ <- either(checkAccess(request, user).toOption, BusinessResult.NotOwner)
  } yield BusinessResult.Ok

  result.fold(identity, identity)
}

private def either[T](opt: Option[T], result: BusinessResult) =
  opt.fold[Either[BusinessResult, T]](Left(result))(Right(_)).right

Pretty straightforward.

A result type declaration is not necessary, I’ve put it just to show what do we have in the end of the for clause.

Adding some implicit magic…

With some implicit “magic” we can transform this code to a bit more concise form:

def processRequestNew(userId: UUID, requestId: UUID): BusinessResult = {
  for {
    user <- getUser(userId) orResult BusinessResult.UserNotFound
    request <- getRequestById(requestId) orResult BusinessResult.RequestNotFound
    _ <- checkAccess(request, user) orResult BusinessResult.NotOwner
  } yield BusinessResult.Ok
}

All the magic is implicit classes that provides orResult function to create Either from Option and Try and an implicit conversion for Either[T, T] (to not write fold(identity, identity)).

Of course, this approach will work not only with enums but with any type.

Conclusion

So, we made a path from old-school Java-style to a concise Scala-style for such scenario of error handling: less verbosity, no ifs.

All the code you may find in a single place on GitHub.

Originally posted on Medium.