Last active
May 17, 2019 05:16
-
-
Save Atry/c7bc7292145e5bff3e57dd504a9a3f2b to your computer and use it in GitHub Desktop.
Revisions
-
Atry revised this gist
May 17, 2019 . 1 changed file with 1 addition and 1 deletion.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal 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 -
Atry created this gist
May 17, 2019 .There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal 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)