How to structure a RESTful API with Spray.io?
Asked Answered
P

2

9

When I'm using Spray.io to develop a RESTful API, how should I structure my application?

I already saw this answer on how to split a Spray application, but I'm not satisfied with it, since it doesn't seem to use the "one actor per request" approach. Can I forward requests from the root actor to other actors in my application based on paths and, inside these actors, define the related routes?

Thanks

Peele answered 15/5, 2014 at 21:32 Comment(0)
F
8

You can certainly forward requests from one actor to another, based on paths or whatever else. Check out my example project (which is a fork of a fork of an example project):

https://github.com/gangstead/spray-moviedb/blob/master/src/main/scala/com/example/routes/ApiRouter.scala

Relavent code from the main actor that receives all requests and routes them to other actors that handle each service:

  def receive = runRoute {
    compressResponseIfRequested(){
      alwaysCache(simpleCache) {
        pathPrefix("movies") { ctx => asb.moviesRoute ! ctx } ~
        pathPrefix("people") { ctx => asb.peopleRoute ! ctx }
      } ~
      pathPrefix("login") { ctx => asb.loginRoute ! ctx } ~
      pathPrefix("account") { ctx => asb.accountRoute ! ctx }
    }
  }

And for example the movies route:

  def receive = runRoute {
    get {
      parameters('query, 'page ? 1).as(TitleSearchQuery) { query =>
        val titleSearchResults = ms.getTitleSearchResults(query)
        complete(titleSearchResults) 
      }~
      path(LongNumber) { movieId =>  
        val movie = ms.getMovie(movieId)
        complete(movie)
      }~
      path(LongNumber / "cast") { movieId =>
        val movieCast = ms.getMovieCast(movieId)
        complete(movieCast)      
      }~
      path(LongNumber / "trailers") { movieId =>
        val trailers = ms.getTrailers(movieId)
        complete(trailers)     
      }        
    }
  }  
Fionnula answered 15/5, 2014 at 22:17 Comment(5)
I was looking at your example here and noticed that you use CDI. Any particular reason to why you chose to use it?Peele
By CDI do you mean dependency injection?Fionnula
Yes. /* Stackoverflow requires more chars so... */Peele
Only for the reasons that dependency injection is good in general. It lets you inject mock objects to unit test the layers independently and let's you swap out layers if you need to.Fionnula
looks like a nice pattern.Noami
C
0

I was struggling a lot with creating first full REST project. The examples I've found was on hello world level... I've read few blogs, few comments and I decided to create example project. It is based on scala/akka/spray/mysql

It full working example with websocket to notify clients that data was changed etc. You can check it out on https://github.com/vixxx123/scalasprayslickexample

Here is sample code of routing from that project:

val personCreateHandler = actorRefFactory.actorOf(RoundRobinPool(2).props(Props[CreateActor]), s"${TableName}CreateRouter")
val personPutHandler = actorRefFactory.actorOf(RoundRobinPool(5).props(Props[UpdateActor]), s"${TableName}PutRouter")
val personGetHandler = actorRefFactory.actorOf(RoundRobinPool(20).props(Props[GetActor]), s"${TableName}GetRouter")
val personDeleteHandler = actorRefFactory.actorOf(RoundRobinPool(2).props(Props[DeleteActor]), s"${TableName}DeleteRouter")

val userRoute =
    pathPrefix("person") {
        pathEnd {
            get {
                ctx => personGetHandler ! GetMessage(ctx, None)
            } ~
            post {
                entity(as[Person]) {
                    entity =>
                        ctx => personCreateHandler ! CreateMessage(ctx, entity)
                }
            }
        } ~
        pathPrefix (IntNumber){
            entityId => {
                pathEnd {
                    get {
                        ctx => personGetHandler ! GetMessage(ctx, Some(entityId))
                    } ~ put {
                        entity(as[Person]) { entity =>
                            ctx => personPutHandler ! PutMessage(ctx, entity.copy(id = Some(entityId)))
                        }
                    } ~ delete {
                        ctx => personDeleteHandler ! DeleteMessage(ctx, entityId)
                    } ~ patch {
                        ctx => personPutHandler ! PatchMessage(ctx, entityId)
                    }
                }
            }
        }
    }

And sample from create actor handler:

override def receive: Receive = {

    case CreateMessage(ctx, person) =>

      val localCtx = ctx
      connectionPool withSession {
        implicit session =>
          try {
            val resId = PersonsIdReturning += person
            val addedPerson = person.copy(id = Some(resId.asInstanceOf[Int]))
            localCtx.complete(addedPerson)
            publishAll(CreatePublishMessage(TableName, localCtx.request.uri + "/" + addedPerson.id.get, addedPerson))
            L.debug(s"Person create success")
          } catch {
            case e: Exception =>
              L.error(s"Ups cannot create person: ${e.getMessage}", e)
              localCtx.complete(e)
          }
      }
  }

There are still two important things missing: oauth2 and push notifications to specific user/connection via websocket

Crawl answered 16/4, 2015 at 13:20 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.