Reputation: 4811
Say I have a controller that has a future call, and then I redirect to another page which expects that call to have computed. Is it possible the future doesn't return fast enough and when I redirect to the other page the data will not be "fresh"?
Example:
class HomeController {
def action1 = Action{
val f1 = Future { ... }
Redirect(routes.HomeController.action2)
}
def action2 = Action {
// expects the call to f1:Future to have executed
}
}
The reason I am asking is should my Service layer return Future's or block? Or should I pass the Future down to the calling code like in my controller.
UserService
save
delete
update
findById
etc. etc..
Should these return Future?
If I expected the call to have computed, I have to block. So is there no hard and fast rule for this?
Upvotes: 0
Views: 63
Reputation: 14414
There's a couple of things in your question that are somewhat tangential, so I'll address them in turn
1) Will the redirect response return before the future returns?
Impossible to tell and, further, it will not behave consistently. For the redirect to not return before the Future
has completed its computation, you should use an async action instead, so that the call doesn't respond with the redirect until the Future
completes:
def action1 = Action.async { request =>
futureComputation().map { _ => Redirect(routes.HomeController.action2) }
}
2) Should my Service layer return a Future
?
The simple answer is yes, if the underlying API is also non-blocking.
The more nuanced answer is yes, even if your service call is blocking, but every other call that is used in your workflow returns a future. This second qualification is about composition and readability.
Suppose the workflow is:
findById
fetchHttpResource
(async get)update
(with data from fetchBecause there is one async component, the workflow should be async and you might write it like this:
def workflow(id:UUID):Future[Unit] = {
val user = UserService.findById(id)
fetchHttpResource(id).map { resource =>
val updatedUser = user.copy(x = resource)
UserService.update(updatedUser)
}
}
If UserService returns Future
's as well, you could use a for-comprehension instead:
def workflow(id: UUID): Future[Unit] = for {
user <- UserService.findById(id)
resource <- fetchHttpResource(id)
_ <- UserService.update(user.copy(x = resource))
} yield {}
which brings me to
3) Is there a hard and fast rule for using Future
's?
Async code is a turtles all the way down affair. Once you have one async call in your chain, your chain has to return a Future
and once you have that requirement, composition via for
, map
, flatMap
, etc. and error handling becomes a lot cleaner among Future
signatures. The additional benefit is that if you have a blocking call now, maybe in the future you can find an async API to use inside that service, your signature doesn't have to change.
For returning a Future
from blocking calls that may fail the best policy is to wrap them with future {}
. If however, you have computed data that cannot fail, using Future.successful()
is the better choice.
Upvotes: 3
Reputation: 181
As your code stands your action1 will kick off the code executed in the future then send the redirect to the browser. The is no guarantee that the code in the future will be done before action2 is called.
Howver, assuming this is play framework, you can wait for the code in the future to complete without blocking the current thread, by using the map or onComplete methods on Future to register callbacks, in which you'd complete the request by sending the redirect. You'll need to change your Action to Action.async.
Upvotes: 0