Has akka-http has abandoned per request actors in favor an anti-pattern DSL?

1,688 views
Skip to first unread message

kraythe

unread,
Apr 2, 2017, 9:09:32 PM4/2/17
to Akka User List
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. 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?




Ryan Tanner

unread,
Apr 3, 2017, 11:52:05 AM4/3/17
to Akka User List
Leaving aside the tone of this post...

Have you looked at the longer example of the DSL?


It has examples of how to delegate an endpoint's business logic off to actors or futures.

Robert Wills

unread,
Apr 3, 2017, 12:00:49 PM4/3/17
to akka...@googlegroups.com
If you have lots of endpoints and you want to split them up into multiple files you can do that using the dsl.  


I don't think your proposal is a good idea.

--
>>>>>>>>>> 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 akka-user+unsubscribe@googlegroups.com.
To post to this group, send email to akka...@googlegroups.com.
Visit this group at https://groups.google.com/group/akka-user.
For more options, visit https://groups.google.com/d/optout.

kraythe

unread,
Apr 3, 2017, 1:06:40 PM4/3/17
to Akka User List
There was no intention in the tone, sorry if it came across that way. It just feels like there is an issue. I will check out the article, thanks.

kraythe

unread,
Apr 3, 2017, 1:18:16 PM4/3/17
to Akka User List
About tone, I am very matter of fact and blunt. Its not intended to offend, just be more efficient. Sorry if you took it badly. 

Ok I reviewed the artilce, In fact I thought i had linked that one. It still leaves a lot of information in the route itself and is still rather simplistic. For example consider the path: 

 pathPrefix("order" / IntNumber) { orderId =>

In this path the implementer assumes the order number is valid, that the user has access to the order, that the order is not blocked according to HAZMAT and DHS regulations from being seen by the user in question and a thousand other examples of problems that could arrise. It just assumes the order is visible. Now if I want to do all of that checking I have to do it in a route because if they give a bad order number I need to route to a bad request, if they try to alter an order they dont have permissions to alter, I have to route to a forbidden, if the order is blocked by DHS regulations from being viewed outside secret channels I have to log the unauthorized attempt, and then route to bad request or fobidden and if the database goes down I have to route to internal error. 

Now down we put all of that logic in the route? It might not even be possible, the logic could be a result of aggregating data together from several other actors and maybe even extra network calls. See where I am going with this? Once we fork off to the actor we have lost the route, we cant get it back to make decisions based on the data in the system. The only way those decisions on the different outcomes can be made is in the actor handling the request. One alternative is to create an intermediary model to be interpreted back in the route but that is introducing a lot of code for what I would consider nominal gain and a potential maintenance nightmare given I might have 300 endpoints. 

See where I am going with this? 

Now alternatively if I could pass some kind of context to the actor, the actor could take care of all of that heavy lifting and simply resume the route when it determined the fate of the request. That would make the system massively easier to use and I think would avoid what I personally see as an anti-pattern. 


On Monday, April 3, 2017 at 10:52:05 AM UTC-5, Ryan Tanner wrote:

kraythe

unread,
Apr 3, 2017, 1:20:59 PM4/3/17
to Akka User List
Yes I know routes can be concatenated from several sources. I addressed that in my original post. It doesn't take away from the core thrust. Even the determination of whether an endpoint ends up in a bad request or an ok or a forbidden could take a significant amount of logic to determine in the real world and some of that logic may be a result of aggregating data from several actors which cant be done in a route without ask pattern and then you are back in imperative programming and there is little point to using actors at all. 
To unsubscribe from this group and stop receiving emails from it, send an email to akka-user+...@googlegroups.com.

Roland Kuhn

unread,
Apr 3, 2017, 3:05:59 PM4/3/17
to akka...@googlegroups.com
Hi Robert,

since a Route is just a function from request to future of response you can easily achieve all that you describe, delegating as much or as little as you wish to actors via the ask pattern (delegating via a parent actor whose ref is known to the route). Commonly extracted metadata are conveniently factored out into custom or composed directives and the actor may respond with HttpResponse for full flexibility in terms of determining the success of the request.

Unfortunately, I am traveling and not at my computer, hence no code.

Regards, Roland 

Sent from my iPhone

kraythe

unread,
Apr 3, 2017, 9:29:07 PM4/3/17
to Akka User List
Roland, 

I am doing that with the low level API right now and it mostly works. Its just unfortunate I dont have access to the tools in the DSL inside the actor because my paths to validation of the logic are quite a bit more rich than a simple determination of whether the attribute is present in the call. Whether the attribute is truly needed or not often depends on context. Like I said it owuld be awesome if inside the actor I could re-enter the dsl in the manner i described in the OP. 

The good news is the low level API is pretty good but it would be nice if I could use some of the tools for pulling out request bodies as json and so on. 

-- Robert

Daniel Stoner

unread,
Apr 4, 2017, 3:38:52 AM4/4/17
to Akka User List
Hi Kraythe,

Perhaps it helps to see a real world example that we've been working on - with a good number of routes involved.

This is from our AkkaHttpServer class. It's job is to inject all the routes (ordersV2, searchv3, searchTerms, persistence) which consist of around 6 actual endpoints per injected class - into the right point in the hierarchy (Below the oAuth2 authenticator and any request/response loggers and whatnot that you may need).

We define index.html and healthcheck route in this class since they are one liners that live above oAuth2 security otherwise we would also inject them independently.

Route indexRoute = get(() -> route(pathSingleSlash(() -> getFromResource("web/index.html"))));
        Route healthCheck = get(() -> path(PATH_HEALTH_CHECK, () -> extractRequestContext(healthCheckHandler::handle)));

        Route apis = route(
                indexRoute,
                healthCheck,
                oauth2Authentication(
                        accessTokenVerifier,
                        route(
                                ordersV2,
                                searchV3,
                                searchTerms,
                                persistence
                        )
                )
        );

        return logRequestResult(
                this::requestMethodAsInfo,
                this::rejectionsAsInfo,
                () -> handleExceptions(
                        exceptionHandlerLogAndReturnInternalError(),
                        () -> handleRejections(
                                rejectionHandlerLogAndReturnNotFound(),
                                () -> apis
                        )
                )
        );

Note the handleExceptions and handleRejections methods. Basically if your in the depths of a route (the bit supposed to handle the request) you have 3 options.

1) Handle the request and reply with a HttpResponse.
return HttpResponse.create().withStatus(StatusCodes.CREATED).addHeader(
                    RawHeader.create(HttpHeaders.LOCATION, location)
            )
2) Reject the request with an explicit Rejection
return reject(Rejections.authorizationFailed());
3) Throw an exception directly
throw new MyCustomException("Something went dodgy here!")

Now how you transpose those rejections or exceptions into a HttpResponse is up to your generic rejection or exception handler right at the very top of your routes. I've included our 'ErrorHandlingDirectives' class which we simply extend (Instead of extending AllDirectives) wherever we need this:

public class ErrorHandlingDirectives extends AllDirectives {

    private static final Logger LOG = LoggerFactory.getLogger(ErrorHandlingDirectives.class);

    public LogEntry requestMethodAsInfo(HttpRequest request, HttpResponse response) {
        String headers = toCensoredHeaderJson(request.getHeaders());

        return LogEntry.create(
                "Server has received a request\n"
                + request.method().name() + " " + request.getUri().toString() + "\n"
                + headers + "\n"
                + "Server responded with a response\n"
                + response.status() + "\n"
                + "Content-Type: " + response.entity().getContentType().toString() + "\n"
                + "Content-Length: " + response.entity().getContentLengthOption().orElse(-1),
                InfoLevel());
    }

    public LogEntry rejectionsAsInfo(HttpRequest request, List<Rejection> rejections) {
        String headers = toCensoredHeaderJson(request.getHeaders());

        return LogEntry.create(
                "Server has received a request\n"
                + request.method().name() + " " + request.getUri().toString() + "\n"
                + headers + "\n"
                + "Server responded with a rejection\n"
                + rejections.stream().map(Rejection::toString).collect(Collectors.joining("\n")),
                InfoLevel());
    }

    public ExceptionHandler exceptionHandlerLogAndReturnInternalError() {
        return ExceptionHandler
                .newBuilder()
                .matchAny(throwable -> extractRequest(request -> {
                    LOG.warn("Error on route: " + request.method().value() + " " + request.getUri().toString() + " " + throwable.getMessage(), throwable);
                    return complete(StatusCodes.INTERNAL_SERVER_ERROR);
                })
                ).build();
    }

    public String toCensoredHeaderJson(Iterable<HttpHeader> headers) {
        return StreamSupport
                .stream(headers.spliterator(), false)
                .map(header -> {
                    if (header instanceof Authorization) {
                        return header.name() + ": CENSORED";
                    }
                    return header.name() + ": " + header.value();
                })
                .collect(Collectors.joining("\n"));
    }

}


So - yes you 'can' write 1 big file with a bazillion routes in it - or you can do what most developers do eventually once things are working and split it down into lots of individual classes with their own hierarchy. Have some general rules for your approach (such as if someone throws a BeanVerifiyException you return a particular type of HTTP status code) and you'll soon be enjoying things again :)

As a general rule - whenever we reach the part of our route where we actually plan to handle a request we generally spawn an actor to do the job. For me it's good that the Actor speaks in terms of messages and failed futures - and doesn't have to worry about 'Oh no I better not throw an exception because I really need to return a HttpResponse of INTERNAL_SERVER_ERROR'. This job can be done in your generic handlers :)

kraythe

unread,
Apr 8, 2017, 12:45:35 AM4/8/17
to Akka User List
A very interesting read and I appreciate you showing examples. What I do wonder about there is what the situation is when the server has to do more than simply return information from a database or other similar web rest-ui simple examples. For example, I have processes that require aggregating data from several actors to complete the process. Now I could do these in a series of ASKs but then that would feel like a complete anti-pattern to me. I would rather model the system as an entirely one way calling pattern where the only future is in the final completion of the route. So here is the challenge: 

You have a route called /customerInfo which finds the search parameters out of the JSON body that is passed to the api. Then it has to invoke calls to two ProductManagerActor and InventoryManagerActor. These actors will send back responses which might result in the task being done but will be much more likely to require calling actor ShippingStatusActor several times to ask for different pieces of data. Once you have collected all of the data then finally you can assemble the JSON response. Yes. I could model this imperatively with futures and using the ask pattern inside the route but then I am massively constrained resources and I have never found that to be scalable under load. Indeed I try to avoid futures as much as I can because of these difficulties. I would prefer instead to have an actor handle the request, recieving messages from the three actors and then when it has assembled all of the data it would generate a response or if there is a timeout or exception it would generate other responses.

Now using the route DSL I wonder how you would manage this process within your route files. At some point you have to jump off the DSL and offload the task to an actor. For example you can decode the parameters and the body and so on using the route but then you have to start the process of collecting this massive amount of data to send back and then you are off the route and your options are now limited as to how you can respond. You cant simply say inside the DataCollectionActor to invoke the bad request response, through the DSL. you now have to use the low level API to signal a response. 

I would be curious how you could handle this use case with the DSL. 

-- Robert

Richard Rodseth

unread,
Apr 8, 2017, 1:56:26 AM4/8/17
to akka...@googlegroups.com
I use per-request actors. The only ask() is in the route, and is sent to a RequestHandler actor. The RequestHandler creates a per-request actor with the sender as a Props/Constructor parameter ("requestor"), and sends it a "Run" message.

The per-request actor can coordinate with as many actors as it needs to. It can use "self" as a "replyTo" property of messages it sends out (tell). It sends the final result back to its "requestor" and stops itself.

With Spray I didn't need the outermost ask(), but I think it's fine having one at the outermost layer.

--
>>>>>>>>>> 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 akka-user+unsubscribe@googlegroups.com.

kraythe

unread,
Apr 8, 2017, 5:31:10 PM4/8/17
to Akka User List
And you accomplish this with the low level or DSL api. 
To unsubscribe from this group and stop receiving emails from it, send an email to akka-user+...@googlegroups.com.

Richard Rodseth

unread,
Apr 8, 2017, 8:34:12 PM4/8/17
to akka...@googlegroups.com
DSL. 
Route contains:

          onSuccess(requestHandler ? RequestHandler.AskForStatus) { ... }


To unsubscribe from this group and stop receiving emails from it, send an email to akka-user+unsubscribe@googlegroups.com.

Dominik Dorn

unread,
Apr 9, 2017, 7:25:36 AM4/9/17
to akka...@googlegroups.com
what a coincidence that I just stumbled upon this thread. I've created a feature request for "actor per request" a few days back in the playframework.

While I was thinking in terms of Play, maybe this is really best suited in the akka-http layer. 
I also liked the approach lightbend took in "lagom".. there they (it least it looks like they do) transparently convert "service calls" into 
actors, input datatypes&route information into a "Request" message and are just waiting for a "Response" message to send back to

 case HttpRequest(POST, Uri.Path("/hello"), _, _, _) =>
          invokeActor
(classOf[HelloActor], requestId, request)
already looks a lot like what I was thinking of. 



--
Dominik Dorn
http://dominikdorn.com
http://twitter.com/domdorn

Skripten, Mitschriften, Lernunterlagen, etc. findest Du auf http://www.studyguru.eu !

Johannes Rudolph

unread,
Apr 18, 2017, 9:56:19 AM4/18/17
to Akka User List
I think there are a few things that should be treated separately:

 * How to organize bigger route structures?
 * How to implement some parts of the route structure as actors?

As Roland wrote before, a Route is just an alias for a function `RequestContext => Future[RouteResult]`. This gives you utmost flexibility to structure and implement your routes in every asynchronous way. Implementing server behavior in terms of the DLS's `Route` enables you to compose your handling logic in a flexible and scalable way while centralizing error and failure logic (as rejection and exception handlers).

The routing DSL provides a composable way to specify route logic. For a bigger service, it might become a challenge how to organize different routes into different files. You can put everything in one file if that's what you like but it's not the recommended way. As a Route can be just a value we don't force you into a specific kind of dependency injection logic or architecture. It's possible to pass context around, use implicits, the cake pattern or any other kind of dependency injection library or architecture. In this way, Akka HTTP gives you layers to work with but it's not a framework that forces you how to structure your application. We know that this leaves some challenges to the user and we know that giving more guidance in the documentation would be helpful (and we are planning to provide more examples and tutorials on those topic in the future).

Using (request) actors to implement parts of the route tree is a technique which is orthogonal to the code structure question. Repeating the important piece: a Route is just an alias for a function `RequestContext => Future[RouteResult]`. That means that you can put in every function which implements this type at every point in the route structures. If your route processing is stateful and should be implemented in an actor that's completely fine. The most general way to implement that in a route is by using

def handleByActor(actor: ActorRef): Route = ctx => (actor ? ctx).mapTo[RouteResult]

and handle that request in the actor.

However, it might be, that the original point of extracting code into an actor isn't as strong a case any more as it was for spray as routes are now just returning a Future. If you organize all asynchronous data collection into methods returning a Future[XYZ], then you can package all validation and processing into directives. After that's done you can express everything in a route without the need for any actors.

One point with the route structure seems to be that it requires closing over the extracting variables to be concise, if you have this kind of route tree

pathPrefix("user" / Segment) { user =>
  session { session =>
    parameters('a, 'b, 'c) { (a, b, c) =>
       userRoute(user, session, a, b, c)
    }
  }
}

and you want to implement the innermost route somewhere else. Then you have to pass all these information explicitly to the inner route. That can be cumbersome if the context you have extracted from is big. However, in most cases the information is still layered in some ways, so it can make sense to package up intermediate information into case class instances or to factor out things differently. Another pattern could be to provide the outer routes or parts of it as another directive so that it can be reused in different parts. We recommend to use routes and directives as they tend to be composable and reusable. If you put code into actors it can be harder to reuse as actor code often relies on particular messages and asynchronous protocols to work correctly.

Johannes

kraythe

unread,
Apr 18, 2017, 11:48:18 PM4/18/17
to Akka User List
Sorry, I have been buried with work. 

The problem with the DSL approach in my opinion is that it leaves too much space for error and strange things happening and at the same time it hamstrings you into a particular flow that may or may not be appropriate to the problem. Just to have some fun, this route doesn't work. It compiles just fine, the server comes up but nothing happens. Spot the problem? 

val route = {
  path("orders") {
    authenticateBasic(realm = "admin area", myAuthenticator) { user =>
      get {
        encodeResponseWith(Deflate) {
          complete {
            // marshal custom object with in-scope marshaller
            retrieveOrdersFromDB
          }
        }
      } ~
      post {
        // decompress gzipped or deflated requests if required
        decodeRequest {
          // unmarshal with in-scope unmarshaller

          entity(as[Order]) { order =>
            complete {
              // ... write order to DB
              "Order received"
            }
          }
        }
      }
    }

  } ~
  // extract URI path element as Int

  pathPrefix("order" / IntNumber) { orderId =>

    pathEnd {
      (put | parameter('method ! "put")) {
        // form extraction from multipart or www-url-encoded forms
        formFields(('email, 'total.as[Money])).as(Order) { order =>
          complete {
            // complete with serialized Future result
            (myDbActor ? Update(order)).mapTo[TransactionResult]
          }
        }
      } ~
      get {
        // debugging helper
        logRequest("GET-ORDER") {
          // use in-scope marshaller to create completer function
          completeWith(instanceOf[Order]) { completer =>
            // custom
            processOrderRequest(orderId, completer)
          }
        }
      }
    } 
    path("items") {
      get {
        // parameters to case class extraction
        parameters(('size.as[Int], 'color ?, 'dangerous ? "no"))
          .as(OrderItem) { orderItem =>
            // ... route using case class instance created from
            // required and optional query parameters
            complete("") // hide
          }
      }
    }
  } ~
  pathPrefix("documentation") {
    // optionally compresses the response with Gzip or Deflate
    // if the client accepts compressed responses
    encodeResponse {
      // serve up static content from a JAR resource
      getFromResourceDirectory("docs")
    }
  } ~
  path("oldApi" / Remaining) { pathRest =>
    redirect("http://oldapi.example.com/" + pathRest, MovedPermanently)
  }
}

If you got that there is one missing ~ between the routes you have a sharp eye but the DSL wont tell you, the code compiles just fine. But there I thought the DSL was supposed to make everything easy. Nope, if you put directives in the wrong order, nest them in the wrong order, or anything like that the code doesnt warn you, it just refuses to run. Yes, you can scream "unit test everything" but really? Is this untyped Ruby here? Testing everything is not a strategy that scales well in business or endures in a long running codebase, its an academic construct that eventually crumbles under the real world. 

The other problem with the routing is that it ties you into a certain set of directives and techs and breaking out of that is painful. Lets say you like the validation checking of Play-json over Spray-json? Can it be integrated with the route? With difficulty maybe but then you dont get the ability to handle errors like you do with the low level API. If the body doesnt parse out to the type the route blows up and the user gets back an internal error. What if I dont want to tell my users how to hack json to feed to my site? What if I dont want actors blowing up on internal errors? If so then I am up a creek of thin ice. 

Subsequent to my OP, I sat down and worked with the DSL for a good week, integrating it into the business project I am working on. It was painful. Missing tilde causes routes to not work, wrong nesting, wrong order of directives, not able to validate my json and return better error structures or handle things conditionally. It turned into a royal nightmare and it was entirely unnecessary. I switched back to using the low level api and other than some annoyances with handling headers that could be nicer, it just worked. I have flexibility to return what I want, when I want. I can offload to an actor that calls 10 other actors and integrates the results of the other actors into one actor and so on. In short, yes it was a bit more verbose but it was clear, easier to understand, easier to maintain, static type checked and solid. 

My personal conclusion is that the DSL is fine for simple projects but once you get into a real world business app, it starts to get in the way and frustrate. 

-- Robert 

Konrad Malawski

unread,
Apr 18, 2017, 11:58:08 PM4/18/17
to akka...@googlegroups.com, kraythe
For starters: It's fine if your preference is to not use the DSL, no one will force you to; and that's also the reason the DSL is a separate artifact.

Also, your route here is definitely multiple things and those should be split up.
Why make one huge route, make many vals with the small pieces (e.g. all the path prefixed parts in a real app would be in different traits in single vars) and then simply combine them. 

If your specific complaint is about the missing ~, that's solved in two ways:
1) just don't use ~. Use concat :-)

concat(
get {
complete("ok")
},
path("hello") {
complete("ok")
}
)

2) JetBrains is actively working on adding Akka support, all across the board, including routes and other things in plain actors etc.
One of the issues I opened and is being worked on right now is the ~ problem: https://youtrack.jetbrains.com/issue/SCL-11632

-- 
Konrad `ktoso` Malawski
Akka @ Lightbend

Michal Borowiecki

unread,
Apr 19, 2017, 4:50:08 AM4/19/17
to akka...@googlegroups.com, kraythe

Hi there,

Just to chip in for balance.

We're using akka http and the DSL in a real world business app.

The fact we don't have hundreds of routes or don't have to call 10s of actors per request doesn't make our app any less a "real world business app".

Just that ours is small. And we're creating more apps this way and they too will be fairly small (call them microservices if you must) and we're happy to use the DSL.

Choice of solution is always context-dependent, so in the context of a big and complicated service perhaps the low-level api can be a better fit.

Not a reason to call the DSL an anti-pattern though, IMO.



Cheers,

Michal

--
Michal Borowiecki
Senior Software Engineer L4
T: +44 208 742 1600


+44 203 249 8448


 
E: michal.b...@openbet.com
W: www.openbet.com
OpenBet Ltd

Chiswick Park Building 9

566 Chiswick High Rd

London

W4 5XT

UK

This message is confidential and intended only for the addressee. If you have received this message in error, please immediately notify the postm...@openbet.com and delete it from your system as well as any copies. The content of e-mails as well as traffic data may be monitored by OpenBet for employment and security purposes. To protect the environment please do not print this e-mail unless necessary. OpenBet Ltd. Registered Office: Chiswick Park Building 9, 566 Chiswick High Road, London, W4 5XT, United Kingdom. A company registered in England and Wales. Registered no. 3134634. VAT no. GB927523612
Reply all
Reply to author
Forward
0 new messages