3
votes

I need to write simple web service with akka-http and reactivemongo.

Function to save data looks like this

 def saveRoute(route: Route):Future[WriteResult] = {
    collection.insert(route)
 }

a code that calls this function looks like this

val userRoutes = {
    logRequestResult("akka-http-microservice") {
      path("routes") {
        (post & entity(as[Route])) { route =>
          Database.saveRoute(route)
        }
      }
    }
  }

I need to return result with inserted ID of Route and do this without making the thread to wait. if try

Database.saveRoute(route).onComplete{
            case Success(r) => complete(r.toString)
            case Failure(e) => complete(e.getMessage)
          }

It cannot compile, because it doesn't return value. I know how to make it in dirty way, but really want to make in appropriate manner.

What should be done in this case?

5
You could use a Promise and put the value in success.sebszyller

5 Answers

6
votes

Use onSuccess to handle the valid response when the future finishes and handleExceptions to handle when the future does not succeed.

   val userRoutes = {
    handleExceptions(mongoDbExceptionHandler) {
      logRequestResult("akka-http-microservice") {
        path("routes") {
          (post & entity(as[Route])) { route =>
            onSuccess(Database.saveRoute(route)) { result =>
              complete(result)
            }
          }
        }
      }
    }
  }

  // Something like this for whatever the exceptions you expect are
  val mongoDbExceptionHandler = ExceptionHandler {
    case ex: MongoDbReadException => complete(HttpResponse(InternalServerError, "No database")))
  }

onSuccess: http://doc.akka.io/docs/akka/2.4.9/scala/http/routing-dsl/directives/future-directives/onSuccess.html

handleExceptions: http://doc.akka.io/docs/akka/2.4.9/scala/http/routing-dsl/exception-handling.html

6
votes

Seems like I've found most efficient way to do this. It's built in onComplete directive

(path("routes" / "add") & post & entity(as[Route])) {
    route =>
      onComplete(routesController.addRoute(route)) {
        case Success(result) => complete(StatusCodes.Created, "OK")
        case Failure(ex) => complete(new ErrorResponse(StatusCodes.InternalServerError.intValue, ErrorResponse.ERROR, ex.getMessage))
      }
  }
1
votes

You can map over the future and then complete the request like below.

val future = Database.saveRoute(route)
val response = future.map(_.getId).recover(_.getMessage)
complete(response)

On a side note, for handling exceptions, it is a good practice to have a ExceptionHandler and wrap it with your route. You can find example here.

1
votes

You have few option i will try to put the most commonly used ones for REST API based solutions:

OnSuccess use it when you want your expectations to be bubbled and handled by expectionHandler

  concat(
    path("success") {
      onSuccess(Future { "Ok" }) { extraction =>
        complete(extraction)
      }
    },
    path("failure") {
      onSuccess(Future.failed[String](TestException)) { extraction =>
        complete(extraction)
      }
    }
  )

https://doc.akka.io/docs/akka-http/current/routing-dsl/directives/future-directives/onSuccess.html

onComplete: When you want to manually handle the exception. Try Monad wrapped.

val route =
  path("divide" / IntNumber / IntNumber) { (a, b) =>
    onComplete(divide(a, b)) {
      case Success(value) => complete(s"The result was $value")
      case Failure(ex)    => complete((InternalServerError, s"An error occurred: ${ex.getMessage}"))
    }
  }

https://doc.akka.io/docs/akka-http/current/routing-dsl/directives/future-directives/onComplete.html

0
votes

How about this, replace:

Database.saveRoute(route)

with:

complete(Database.saveRoute(route).map(_.toString).recover(_.getMessage))