Thursday, April 3, 2014

Raising Option[T] to Try[T] - Handling error situations with aplomb

I've started using this pattern for dealing with situation where functions I'm working with may return an Option[T] for a given query, but the contextual meaning of returning None is really an error case. A good example of this is looking up an Item in a database by it's ID based on a shopping cart. If the application receives an item ID during a shopping cart process of an item that doesn't exist in the DB, then returning None on the DAO access is fine, but the upshot is an error condition. The application has received bad data somewhere along the way, and this should be manifested as an Exception state, which I'm choosing to encapsulate in a Try[T] so I can pass it cleanly up the stack rather than violating SOLID by throwing an exception, which I know is a subject of some debate.

To help with this, I wrote a simple wrapper class that I've called MonadHelper thusly:

object MonadUtil {
  implicit def option2wrapper[T](original: Option[T]) = new OptionWrapper(original)

  class OptionWrapper[T](original: Option[T]) {
    def asTry(throwableOnNone: Throwable) = original match {
      case None => Failure(throwableOnNone)
      case Some(v) => Success(v)
    }
  }
}

This allows one to construct a for comprehension elevating None returns to an error state somewhat gracefully like this slightly contrived example:

case class CartItemComposite(account: Tables.AccountRow, item: Item)

trait AccountDAO {
  def findById(userId: Long): Option[Tables.AccountRow]
}
trait ItemDAO {
  def findById(itemId: Long): Option[Item]
}

def findShoppingCartItem(itemId: Long, userId: Long)(userDAO: AccountDAO, itemDAO: ItemDAO): Try[CartItemComposite] = {
  for {
    user <- userDAO.findById(userId).asTry(new Throwable("Failed to find user for id " + userId))
    item <- itemDAO.findById(itemId).asTry(new Throwable("Failed to find item for id " + itemId))
  } yield CartItemComposite(user, item)
}


But you get the idea. You can check a set of conditions for validity, giving appropriate error feedback at each step along the way instead of losing the error meaning as you would with simple Option[T] monads in a way that looks less than insane.

Don't know if this is a great pattern yet, but, I'm giving it a whirl!

No comments:

Post a Comment