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 2

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 (или A Future[Result])

Вы получаете ошибку CouchbaseController.scala:30:19: Cannot write an instance of Unit to HTTP response. Try to define a Writeable[Unit], потому что вы ничего не возвращаете. Здесь ожидается A Result.

Обрабатывать фьючерсные цепочки

Кроме того, вы должны обрабатывать вызов нескольких фьючерсов. Если вы этого не сделаете, вы получите молчаливые ошибки даже если клиент получил ответ 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))

}