Reputation: 303
Q: Where is the correct place to be calling Ok() to send http response from an async database call?
I have taken the very basic Scala Play framework tutorial play-scala-starter-example as a starting point and adding some additional basic Controller / Service classes that make use of ReactiveCouchbase for database access.
The application successfully:
I am new to Scala/Play and can't work out the correct way to succesfully write the JSON back to the http response using Ok(), when the async db call completes.
Inside the Controller class is the following function:
def storeAndRead() = Action {
testBucket
.insert[JsValue]("key1", Json.obj("message" -> "Hello World", "type" -> "doc"))
val res = testBucket
.get("key1")
.map(i => Json.toJson(i.get))
.map(j => Ok(j)) // PROBLEM LINE
}
Looking at the "//PROBLEM LINE", having the Ok() call inside the map leads to a compile error:
CouchbaseController.scala:30:19: Cannot write an instance of Unit to HTTP response. Try to define a Writeable[Unit]
Placing the call to Ok() later on, fails with a different compile error:
def storeAndRead() = Action {
testBucket
.insert[JsValue]("key1", Json.obj("message" -> "Hello World", "type" -> "doc"))
val res = testBucket
.get("key1")
.map(i => Json.toJson(i.get))
Ok(res)
}
Compile error:
CouchbaseController.scala:35:7: Cannot write an instance of scala.concurrent.Future[play.api.libs.json.JsValue] to HTTP response. Try to define a Writeable[scala.concurrent.Future[play.api.libs.json.JsValue]]
In this second case, I believe the issue is that the Future may not yet have completed when Ok() is called?
Lastly, I have tried placing the call to Ok() inside an onSuccess() function, in an effort to ensure it is called after the async function has completed:
def storeAndRead() = Action {
testBucket
.insert[JsValue]("key1", Json.obj("message" -> "Hello World", "type" -> "doc"))
val res = testBucket
.get("key1")
.map(i => Json.toJson(i.get))
.onSuccess {
//case doc => Console.println("completed: " + doc)
case doc => Ok(doc)
}
}
Again...compilation error:
CouchbaseController.scala:22:24: overloaded method value apply with alternatives:
[error] (block: => play.api.mvc.Result)play.api.mvc.Action[play.api.mvc.AnyContent] <and>
[error] (block: play.api.mvc.Request[play.api.mvc.AnyContent] => play.api.mvc.Result)play.api.mvc.Action[play.api.mvc.AnyContent] <and>
[error] [A](bodyParser: play.api.mvc.BodyParser[A])play.api.mvc.ActionBuilder[play.api.mvc.Request,A]
[error] cannot be applied to (Unit)
[error] def storeAndRead() = Action {
Question:
I am clearly missing something fairly fundamental:
Where should Ok() be called in this kind of basic scenario? I assume it needs to be called as a result of a callback when the async db request has completed?
What's the correct and appropriate way to structure this in an async way for Scala/Play?
Upvotes: 2
Views: 1130
Reputation: 727
Action.async
Play knows how to handle Future
(async call). You have to use Action.async
.
For instance :
def myAction = Action.async {
// ...
myFuture.map(resp => Ok(Json.toJson(resp)))
}
In your case :
def storeAndRead() = Action.async {
// by the way, the expression behind probably returns a future, you should handle it
testBucket
.insert[JsValue]("key1", Json.obj("message" -> "Hello World", "type" -> "doc"))
testBucket
.get("key1")
.map(i => Json.toJson(i.get))
.map(j => Ok(j))
}
Result
(or a Future[Result]
)You get the error CouchbaseController.scala:30:19: Cannot write an instance of Unit to HTTP response. Try to define a Writeable[Unit]
because you don't return anything. A Result
is expected here.
Futhermore, you should handle the call of several Futures. If you don't, you will get silent errors even if the client received the http response.
For instance :
def storeAndRead() = Action.async {
for {
_ <- testBucket.insert[JsValue]("key1", Json.obj("message" -> "Hello World", "type" -> "doc"))
value <- testBucket.get("key1")
} yield Ok(Json.toJson(value))
}
Upvotes: 4