Scala / Play: Как записать результат JSON из асинхронного вызова db в http Ok
Вопрос: Где находится правильное место вызова Ok() для отправки http-ответа от асинхронного вызова базы данных?
Я взял очень простой учебник Scala Play framework play-scala-starter-example в качестве отправной точки и добавил некоторые дополнительные базовые классы контроллеров / служб, которые используют ReactiveCouchbase для доступа к базе данных.
Приложение успешно:
- подключается к Couchbase
- сохраняет документ JSON в Couchbase
- извлекает сохраненный документ JSON из Couchbase
- записывает содержимое JSON в консоль
Я новичок в Scala / Play и не могу найти правильный способ успешно записать JSON обратно в ответ http с помощью Ok (), когда асинхронный вызов db завершается.
Внутри класса контроллера находится следующая функция:
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
}
Глядя на "/ / проблемную строку", имея вызов Ok() внутри карты приводит к компиляции Ошибка:
CouchbaseController.scala:30:19: Cannot write an instance of Unit to HTTP response. Try to define a Writeable[Unit]
При последующем вызове функции Ok() происходит сбой с другой ошибкой компиляции:
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)
}
Ошибка компиляции:
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]]
В этом втором случае, я полагаю, проблема заключается в том, что будущее может еще не завершиться, когда вызывается Ok ()?
Наконец, я попытался поместить вызов Ok () внутри функции onSuccess (), чтобы убедиться, что он вызывается после завершения асинхронной функции:
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)
}
}
Снова...сборник Ошибка:
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 {
Вопрос:
Я явно упускаю что-то довольно фундаментальное:
Где должен быть вызван Ok() в этом базовом сценарии? Я предполагаю, что он должен быть вызван в результате обратного вызова, когда асинхронный запрос db завершен?
Как правильно и уместно структурировать это асинхронным способом для Scala / Play?
1 ответ:
Обрабатывать будущие результаты с помощью
Action.async
Play знает, как обращаться с
Future
(асинхронный вызов). Вы должны использоватьAction.async
.Например:
def myAction = Action.async { // ... myFuture.map(resp => Ok(Json.toJson(resp))) }
В вашем случае:
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)) }
Вы должны вернуть a
Result
(или AFuture[Result]
)Вы получаете ошибку
CouchbaseController.scala:30:19: Cannot write an instance of Unit to HTTP response. Try to define a Writeable[Unit]
, потому что вы ничего не возвращаете. Здесь ожидается AResult
.Обрабатывать фьючерсные цепочки
Кроме того, вы должны обрабатывать вызов нескольких фьючерсов. Если вы этого не сделаете, вы получите молчаливые ошибки даже если клиент получил ответ http.Например:
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)) }