Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Select an option

  • Save Atry/c7bc7292145e5bff3e57dd504a9a3f2b to your computer and use it in GitHub Desktop.

Select an option

Save Atry/c7bc7292145e5bff3e57dd504a9a3f2b to your computer and use it in GitHub Desktop.

Revisions

  1. Atry revised this gist May 17, 2019. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion Improve Akka HTTP DSL with the help of Dsl.scala.md
    Original file line number Diff line number Diff line change
    @@ -62,7 +62,7 @@ For example, you can use `com.thoughtworks.dsl.keywords.Using` to automatically
    // Sbt settings for the `Using` keyword
    libraryDependencies += "com.thoughtworks.dsl" %% "keywords-using" % "1.2.0"

     

    import akka.http.scaladsl.server.Directives._
    import com.thoughtworks.dsl.keywords.Using
  2. Atry created this gist May 17, 2019.
    133 changes: 133 additions & 0 deletions Improve Akka HTTP DSL with the help of Dsl.scala.md
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,133 @@
    ## Akka HTTP DSL in continuation-passing style

    Akka HTTP provides a powerful Scala DSL to create routes to handle HTTP requests. The DSL contains a set of `Directive`s to extract information or manipulate requests or responses.

    For example, the following `cpsRoute` accepts a `GET` request and extracts query parameters `p1` and `p2` with the help of `get` and `parameters` directive. If an HTTP client send a `GET` request to the URL `/?p1=Hello&p2=World`, it will receive the response of plain text `Hello, World!`.

    import akka.http.scaladsl.server._, Directives._
    def cpsRoute: Route = {
    get {
    parameters("p1", "p2") { (p1, p2) =>
    complete(s"$p1, $p2!")
    }
    }
    }

    What is unusual from PHP or other HTTP server API is that you cannot get the result of `parameters` directive from the return value. Instead, you must pass a callback function to handle the result. This API style in Akka HTTP DSL is called CPS (continuation-passing style), which will cause the infamous "callback hell" problem when there are lots of directives. Since each directive requires an indentation level and a pair of curly brackets, the curly brackets can become deeply nested and the code become too complicated to read.

    ## Escape from callback hell

    Alternative to Akka HTTP's continuation-passing style DSL, I recently released the library [Dsl.scala-akka-http](https://github.com/ThoughtWorksInc/Dsl.scala-akka-http) to provide direct style DSL for Akka HTTP.

    `Dsl.scala-akka-http` is based on [Dsl.scala](https://github.com/ThoughtWorksInc/Dsl.scala), which provides some compiler plugins to perform CPS-transformation. The following settings enable those compiler plugins for sbt projects.


    // Common sbt settings for Dsl.scala
    addCompilerPlugin("com.thoughtworks.dsl" %% "compilerplugins-bangnotation" % "1.2.0")
    addCompilerPlugin("com.thoughtworks.dsl" %% "compilerplugins-reseteverywhere" % "1.2.0")

    In Dsl.scala, each CPS operation can be written in direct style as a !-notation, and at compile time, the user written direct style code will be automatically translated to CPS function calls. You can check [this documentation](https://javadoc.io/page/com.thoughtworks.dsl/dsl_2.12/latest/com/thoughtworks/dsl/index.html) for the details of `Dsl.scala` and !-notation. For now, in this Akka HTTP case, the CPS operation is `TApply`, which can be added to sbt projects with the following settings:

    // Sbt settings for the `TApply` keyword
    libraryDependencies += "com.thoughtworks.dsl" %% "keywords-akka-http-tapply" % "1.0.0"

    Then you can create Akka HTTP routes in direct style. For example, the following `directStyleRoute` provides the same functionality as `cpsRoute`.

    import akka.http.scaladsl.server._, Directives._
    import com.thoughtworks.dsl.keywords.akka.http.TApply
    def directStyleRoute: Route = {
    !TApply(get)
    val (p1, p2) = !TApply(parameters("p1", "p2"))
    complete(s"$p1, $p2!")
    }

    Those `!TApply` are direct style operator to extract the result from directive as return values instead of callback function parameters.

    Alternatively, you can use direct style DSL only for `parameters` directive, and leave `get` directive in CPS style, as shown in the following `mixStyleRoute`:

    def mixStyleRoute: Route = {
    get {
    val (p1, p2) = !TApply(parameters("p1", "p2"))
    complete(s"$p1, $p2!")
    }
    }

    ## Heterogenous library defined keywords

    Semantically, `TApply`, along with a !-notation, can be considered a library defined keyword to perform special operations that are impossible to be implemented in an ordinary Scala library. Each keyword like `TApply` can be used together with other keywords.

    For example, you can use `com.thoughtworks.dsl.keywords.Using` to automatically manage resources, as shown below:


    // Sbt settings for the `Using` keyword
    libraryDependencies += "com.thoughtworks.dsl" %% "keywords-using" % "1.2.0"



    import akka.http.scaladsl.server.Directives._
    import com.thoughtworks.dsl.keywords.Using
    import java.nio.file.Files
    import java.nio.file.Paths
    import scala.collection.JavaConverters._

    def currentDirectoryRoute = {
    pathPrefix("current-directory") {
    get {
    val glob = !TApply(parameters("glob"))
    val currentDirectory = !Using(Files.newDirectoryStream(Paths.get(""), glob))
    path("file-names") {
    complete(currentDirectory.iterator.asScala.map(_.toString).mkString(","))
    } ~ path("number-of-files") {
    complete(currentDirectory.iterator.asScala.size.toString)
    }
    }
    }
    }


    With the help of the additional `Using` keyword, the `currentDirectoryRoute` will open the `currentDirectory`, which is a closeable `DirectoryStream` created from `Files.newDirectoryStream()`, according to the glob pattern extract from the query parameter, and automatically close the directory after the HTTP request is processed completely. For example, you will find a comma separated file list of all `*.md` file names of current directory at the URL `/current-directory/file-names?glob=*.md`, and the URL `/current-directory/number-of-files?glob=*.md` contains the number of `*.md` files in current directory. The `DirectoryStream` will be open and closed when processing either of the URLs.

    Resource management is relatively trivial in Akka HTTP DSL, without the help of Dsl.scala, as shown below:


    def akkaHttpCurrentDirectoryRoute: Route = {
    parameters("glob") { glob =>
    val currentDirectory = Files.newDirectoryStream(Paths.get(""), glob)
    mapRouteResultFuture { future =>
    future.transform { result: Try[RouteResult] =>
    Try {
    currentDirectory.close()
    }.flatMap { _: Unit =>
    result
    }
    }
    } {
    pathPrefix("current-directory") {
    get {
    path("file-names") {
    complete(currentDirectory.iterator.asScala.map(_.toString).mkString(","))
    } ~ path("number-of-files") {
    complete(currentDirectory.iterator.asScala.size.toString)
    }
    }
    }
    }
    }
    }


    You can compare `akkaHttpCurrentDirectoryRoute` with `currentDirectoryRoute` to learn how Dsl.scala simplifies the query parameter extraction as well as resource management.

    ## Conclusion

    In this article, I introduced Dsl.scala-akka-http, a library based on Dsl.scala to provide direct style DSL keyword `TApply` for Akka HTTP. There are other library-defined keywords in Dsl.scala, for collection manipulation, null-safety, exception handling, parallel execution, etc. You can find them in [Dsl.scala's Scaladoc](https://javadoc.io/page/com.thoughtworks.dsl/dsl_2.12/latest/com/thoughtworks/dsl/keywords/index.html).

    I created the library Dsl.scala during my career in ThoughtWorks, and I will keep maintaining this library in my part time, though I have resigned from ThoughtWorks recently. Since I am back to the job market, you can contact me if you recognize my expertise in my open source contributions. I am currently seeking for career opportunity in California, especially in the San Francisco bay area or San Diego.

    ## Links

    * [Dsl.scala project page](https://github.com/ThoughtWorksInc/Dsl.scala)
    * [Dsl.scala-akka-http project page](https://github.com/ThoughtWorksInc/Dsl.scala-akka-http)
    * [Akka HTTP](https://doc.akka.io/docs/akka-http)
    * [Scaladoc for `TApply`](https://javadoc.io/page/com.thoughtworks.dsl/keywords-akka-http-tapply_2.12/latest/com/thoughtworks/dsl/keywords/akka/http/TApply.html)
    * [My CV](https://www.yang-bo.com/assets/cv.pdf)