I was really excited about akka-http as I would be able to unburden my code 
from the baggage of play and handle my server side as a pure akka actors 
app but unless I am much mistaken something is dreadfully amiss with the 
implementation. 

One of the main core features is the actor paradigm and the integration of 
a rich actor system. However, the preferred approach to akka-http seems to 
be a throwback to one file programming. The main reason it seems this way 
is the DSL. Take this example from a tutorial: 

 path("bank" / IntNumber) { id =>
        get {
          complete {
            getById(id).map{result =>
              if(result.isDefined)
                  HttpResponse(entity =write(result.get))
              else
                HttpResponse(entity ="This bank does not exist")
            }


          }
        }
      }~
        path("bank" / "update") {
          post {
            entity(as[String]) { bankJson =>
              complete {
                val bank =parse(bankJson).extract[Bank]
                update(bank).map{result => HttpResponse(entity ="Bank has 
 been updated successfully")}
              }
            }
          }
        }
    }
  }


Simple enough right? Too me I see the start of an anti-pattern but lets 
look further. It gets worse though, quickly,  as shown in the akka-http 
documentation here 
<http://doc.akka.io/docs/akka-http/10.0.0/scala/http/routing-dsl/index.html#longer-example>.
 
Still not bothered? The problem is that these examples are shallow and not 
rooted in the real world. In the bank application above would be hundreds 
of endpoints and each endpoint would have to validate data send by the 
user, check to see whether that data correct against the database and a 
dozen other things that would alter the nature of the return type to a  bad 
request or internal error. The banking app would also have to log the 
problems so forensics can be done on malicious users. Just taking one route 
"deposit" would be several hundred lines of code INSIDE the route. However, 
it seems that there is no way to break off the route, offload it to another 
component (such as a Per Request Actor) and then continue the DSL where you 
left off. I had the chance to see for an app in another company that was 
asking my advice and their route is 12k lines long and at one point nested 
30 levels deep.

Now I know what you might say, "But Robert, you can break up the route into 
multiple files" which is true but something has to manually concatenate all 
of those routes together and they cant be done off of the main route. once 
you are in the routes DSL you are stuck there. Sure, you can call an actor 
with a future to do a completion but that actor itself might return data 
that requires a different kind of completion based upon certain criteria 
such as whether the user has had their account suspended. So if your 
completions are diverse, how do you break up the route? 

Now if someone has answers to these issues I would love to hear them but 
after researching I found that basically PRA's are deprecated in favor of a 
"convenient" DSL that entraps the user. For my purposes I opted to go with 
the low level API and factor off the route dispatching to a routing actor 
(yes, I know this is what the materializer does) and then just pull out 
route data the old fashioned way. My router,  does path checking and then 
dispatches to another actor to handle that specific request and then sends 
the HttpResponse entity back to the sender which completes the ask and the 
route. My startup looks like this: 

  val serverSource: Source[Http.IncomingConnection, Future[Http.
ServerBinding]] =
    Http().bind(interface = "localhost", port = 8080)
  log.info("Server online at http://localhost:8080";)
  val bindingFuture: Future[Http.ServerBinding] =
    serverSource.to(Sink.foreach { connection => // foreach materializes 
the source
      import akka.pattern.ask
      println("Accepted new connection from " + connection.remoteAddress)
      connection.handleWithAsyncHandler(request => (httpRouter ? request).
mapTo[HttpResponse], parallelism = 4)
    }).run()


A snippet of the router looks like this. 

class HttpRequestRouter extends Actor {
  protected val log = Logging(context.system, this)


  override def receive: Receive = {
    case request: HttpRequest =>
      val requestId = UUID.randomUUID()
      request match {
         case HttpRequest(GET, Uri.Path("/"), _, _, _) =>
          notFound(requestId, request) // todo Implement this
        case HttpRequest(POST, Uri.Path("/hello"), _, _, _) =>
          invokeActor(classOf[HelloActor], requestId, request)
        case HttpRequest(GET, Uri.Path("/users"), _, _, _) =>
          invokeActor(classOf[ListUsersActor], requestId, request)
        case HttpRequest(GET, Uri.Path("/addUser"), _, _, _) =>
          invokeActor(classOf[AddUserActor], requestId, request)
         case uri =>
          notFound(requestId, request)
      }
    case msg => log.warning("Received unknown message: {}", msg)
  }


  private def invokeActor(actorType: Class[_], requestId: UUID, request: 
HttpRequest) = {
    context.actorOf(Props(actorType, sender(), requestId, request), 
requestId.toString)
  }}

This allows me to fork off PRAs as needed but it kind of stinks in one way 
because there are a lot of tools in the DSL for unpacking entities and so 
on that I cant use, or rather if there is a way I and neither I nor anyone 
within the reach of google has figured it out. 

So what am I missing? Do people really love this monstrous DSL even though 
in a 100 endpoint system the thing will be gargantuan? Is there a means to 
fork off at any point in the DSL and then "reboot the stream"? It would be 
nice if some of the DSL tools could be invoked arbitrarily inside the PRAs 
on the request object like. 
class OrderPRA(replyTo: ActorRef, requestId: UUID, request: HttpRequest) {

  // ... code
  sender.tell(withRequest(request) {
    entity(as[Order]) { order =>
            complete {
              // ... write order to DB
              "Order received"
            }
          }
  }), self)
}


Opinions? Thoughts?




-- 
>>>>>>>>>>      Read the docs: http://akka.io/docs/
>>>>>>>>>>      Check the FAQ: 
>>>>>>>>>> http://doc.akka.io/docs/akka/current/additional/faq.html
>>>>>>>>>>      Search the archives: https://groups.google.com/group/akka-user
--- 
You received this message because you are subscribed to the Google Groups "Akka 
User List" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to [email protected].
To post to this group, send email to [email protected].
Visit this group at https://groups.google.com/group/akka-user.
For more options, visit https://groups.google.com/d/optout.

Reply via email to