Blankman
Blankman

Reputation: 267040

Why should you return an Option[Model] when fetching by Id?

My entire database layer is currently returning a Future[Option[Model]] for all my models.

I find that it is just making working with Futures more difficult, and more importantly I haven't come across a use case where it even makes sense to return an Option.
My application should basically crash if the Model was not found by the Id.

I am not exactly sure why I went with Option[Model], but what is considered a best practise?

I know in RubyonRails, when you are fetching by Id i.e. Model.find(123), it will throw an exception if it isn't found.

Upvotes: 0

Views: 118

Answers (3)

Anna Zubenko
Anna Zubenko

Reputation: 1663

I think you should stick to Future[Option[Model]] exactly for the reason of not throwing and not catching exceptions. If method return type is Option[Model], you can't miss handling the case of model being absent. Type signature hints there might be the case of nothing to return. Caller has to explicitly map or fold over Option to get rid of it and process inner Model.

Since all exceptions are unchecked (and checked exceptions are just a train wreck), compiler can't hint you to handle an exception, so you might get a surprise at runtime. Scala is a compiled and statically typed language, and using types to describe return data is a pretty damn good idea since compiler will help you to get everything right. This is different in Ruby! You have to understand the benefits of static typing and compile- vs runtime error handling.

Same applies for handling failed Futures: if the Future is always successful and failed Futures only indicate some unexpected error, never regular stuff like 'model not found', you can just map over them without explicitly checking if they're failed or not.

In your case, Future[Option[Model] gives you better error handling and program flow control. If your DAO just throws exceptions, you'll get a crashed app with some ugly stack trace in case of error. But if your DAO returns Future[Option[Model]], caller code might look smth like this:

DAO.findById(id).map { maybeModel: Option[Model] ⇒
  maybeModel match {
    case Some(model) ⇒ processModel(model)
    case None        ⇒ exitWithError(s"Could not find model with id = $id")
  }
}

(code is so verbose for readability purposes)

The above code gives you more control over exit points in your app, which might be super useful if your requirements change and you must not crash the app if model is not found. For example, you might find all calls to exitWithError and replace them with logError or showErrorMessage kind of logic. This is nearly impossible if you go with exceptions.

Another example: suppose you have 2 DAO methods: findById(id: Long): Future[Option[Model]] and countById(id: Long): Future[Int]. Their type signatures suggest that findById might have nothing to return back to caller, which is indicated by Option. countById will always have to return something, which is suggested by plain Int. When coding against this API, caller understands the semantics of both methods. When calling findById, they should think of 2 explicit cases: when model is found and when it's not. When calling countById, just use whatever value is returned, always an Int.

Hope this makes it more clear :) Feel free to ask more questions.

Upvotes: 0

Knows Not Much
Knows Not Much

Reputation: 31546

I find it very convenient when you can use Options as collections.

Meaning that rather than writing nested if/else statements which check for null and then extract values.... you can treat the output as a collection and process it.

This results in code which is flat rather than the traditional nested if/else way.

You can read about the benefits of using Options here

http://danielwestheide.com/blog/2012/12/19/the-neophytes-guide-to-scala-part-5-the-option-type.html

Upvotes: 1

Alexey Romanov
Alexey Romanov

Reputation: 170745

I'd certainly just go with Future[Model] in this situation. Even if the application shouldn't "basically crash", Future can fail and this can be easily handled. Use Future[Option[...]] where it makes sense to succeed with no result (e.g. where you have a nullable association in a database).

Upvotes: 3

Related Questions