Let’s take TinyWeb and transform it into Scala. We’ll do this a bit at a time so we can show how our Scala code can work with the existing Java code. The overall shape of the framework will be similar to the Java version, but we’ll take advantage of some of Scala’s functional features to make the code more concise.
We’ll start with our view code. In Java, we used the classic Strategy pattern. In Scala, we’ll stick with the Strategy pattern, but we’ll use higher-order functions for our strategy implementations. We’ll also see some of the benefits of expressions over statements for control flow.
The biggest change we’ll make is to the view-rendering code. Instead of using Functional Interface in the form of RenderingStrategy, we’ll use a higher-order function. We go over this replacement in great detail in Pattern 1, Replacing Functional Interface.
Here’s our modified view code in its full functional glory:
ScalaExamples/src/main/scala/com/mblinn/mbfpp/oo/tinyweb/stepone/View.scala | |
| package com.mblinn.mbfpp.oo.tinyweb.stepone |
| import com.mblinn.oo.tinyweb.RenderingException |
| |
| trait View { |
| def render(model: Map[String, List[String]]): String |
| } |
| class FunctionView(viewRenderer: (Map[String, List[String]]) => String) |
| extends View { |
| def render(model: Map[String, List[String]]) = |
| try |
| viewRenderer(model) |
| catch { |
| case e: Exception => throw new RenderingException(e) |
| } |
| } |
We start off with our View trait. It defines a single method, render, which takes a map representing the data in our model and returns a rendered String.
| trait View { |
| def render(model: Map[String, List[String]]): String |
| } |
Next up, let’s take a look at the body of FunctionView. The code below declares a class that has a constructor with a single argument, viewRenderer, which sets an immutable field of the same name.
| class FunctionView(viewRenderer: (Map[String, List[String]]) => String) |
| extends View { |
| classBody |
| } |
The viewRenderer function parameter has a rather strange-looking type annotation, (Map[String, String]) => String. This is a function type. It says that viewRenderer is a function that takes a Map[String, String] and returns a String, just like the renderView on our Java RenderingStrategy.
Next, let’s take a look at the render method itself. As we can see from the code below, it takes in a model and runs it through the viewRender function.
| def render(model: Map[String, List[String]]) = |
| try |
| viewRenderer(model) |
| catch { |
| case e: Exception => throw new RenderingException(e) |
| } |
Notice how there’s no return keyword anywhere in this code snippet? This illustrates another important aspect of functional programming. In the functional world, we program primarily with expressions. The value of a function is just the value of the last expression in it.
In this example, that expression happens to be a try block. If no exception is thrown, the try block takes on the value of its main branch; otherwise it takes on the value of the appropriate case clause in the catch branch.
If we wanted to supply a default value rather than wrap the exception up into a RenderException, we can do so just by having the appropriate case branch take on our default, as illustrated in the following code:
| try |
| viewRenderer(model) |
| catch { |
| case e: Exception => "" |
| } |
Now when an exception is caught, the try block takes on the value of the empty string.
Now let’s take a look at transforming our controller code into Scala. In Java we used the Controller interface and the TemplateController class. Individual controllers were implemented by subclassing TemplateController.
In Scala, we rely on function composition just like we did with our views by passing in a doRequest function when we create a Controller:
ScalaExamples/src/main/scala/com/mblinn/mbfpp/oo/tinyweb/steptwo/Controller.scala | |
| package com.mblinn.mbfpp.oo.tinyweb.steptwo |
| |
| import com.mblinn.oo.tinyweb.HttpRequest |
| import com.mblinn.oo.tinyweb.HttpResponse |
| import com.mblinn.oo.tinyweb.ControllerException |
| import com.mblinn.oo.tinyweb.RenderingException |
| |
| trait Controller { |
| def handleRequest(httpRequest: HttpRequest): HttpResponse |
| } |
| |
| class FunctionController(view: View, doRequest: (HttpRequest) => |
| Map[String, List[String]] ) extends Controller { |
| |
| def handleRequest(request: HttpRequest): HttpResponse = { |
| var responseCode = 200; |
| var responseBody = ""; |
| |
| try { |
| val model = doRequest(request) |
| responseBody = view.render(model) |
| } catch { |
| case e: ControllerException => |
| responseCode = e.getStatusCode() |
| case e: RenderingException => |
| responseCode = 500 |
| responseBody = "Exception while rendering." |
| case e: Exception => |
| responseCode = 500 |
| } |
| |
| HttpResponse.Builder.newBuilder() |
| .body(responseBody).responseCode(responseCode).build() |
| } |
| } |
This code should look fairly similar to our view code. This is a fairly literal translation of Java into Scala, but it’s not terribly functional because we’re using the try-catch as a statement to set the values of responseCode and responseBody.
We’re also reusing our Java HttpRequest and HttpResponse. Scala provides a more concise way to create these data-carrying classes, called case classes. Using the try-catch as an expression, as well as using case classes, can help cut down on our code significantly.
We’ll make both of these changes in our next transformation.
Let’s start by switching over to case classes instead of using the Builder pattern. It’s as simple as the code below:
ScalaExamples/src/main/scala/com/mblinn/mbfpp/oo/tinyweb/stepthree/HttpData.scala | |
| package com.mblinn.mbfpp.oo.tinyweb.stepthree |
| |
| case class HttpRequest(headers: Map[String, String], body: String, path: String) |
| case class HttpResponse(body: String, responseCode: Integer) |
We can create new HttpRequest and HttpResponse objects easily, as the following REPL output shows:
| scala> val request = HttpRequest(Map("X-Test" -> "Value"), "requestBody", "/test") |
| request: com.mblinn.mbfpp.oo.tinyweb.stepfour.HttpRequest = |
| HttpRequest(Map(X-Test -> Value),requestBody,/test) |
| |
| scala> val response = HttpResponse("requestBody", 200) |
| response: com.mblinn.mbfpp.oo.tinyweb.stepfour.HttpResponse = |
| HttpResponse(requestBody,200) |
At first glance, this might seem similar to using a Java class with constructor arguments, except that we don’t need to use the new keyword. However, in Pattern 4, Replacing Builder for Immutable Object, we dig deeper and see how Scala’s ability to provide default arguments in a constructor, the natural immutability of case classes, and the ability to easily create a new instance of a case class from an existing instance lets them satisfy the intent of the Builder pattern.
Let’s take a look at our second change. Since a try-catch block in Scala has a value, we can use it as an expression rather than as a statement. This might seem a bit odd at first, but the upshot is that we can use the fact that Scala’s try-catch is an expression to simply have the try-catch block take on the value of the HttpResponse we’re returning. The code to do so is below:
ScalaExamples/src/main/scala/com/mblinn/mbfpp/oo/tinyweb/stepthree/Controller.scala | |
| package com.mblinn.mbfpp.oo.tinyweb.stepthree |
| import com.mblinn.oo.tinyweb.ControllerException |
| import com.mblinn.oo.tinyweb.RenderingException |
| |
| trait Controller { |
| def handleRequest(httpRequest: HttpRequest): HttpResponse |
| } |
| class FunctionController(view: View, doRequest: (HttpRequest) => |
| Map[String, List[String]] ) extends Controller { |
| def handleRequest(request: HttpRequest): HttpResponse = |
| try { |
| val model = doRequest(request) |
| val responseBody = view.render(model) |
| HttpResponse(responseBody, 200) |
| } catch { |
| case e: ControllerException => |
| HttpResponse("", e.getStatusCode) |
| case e: RenderingException => |
| HttpResponse("Exception while rendering.", 500) |
| case e: Exception => |
| HttpResponse("", 500) |
| } |
| } |
This style of programming has a couple of benefits. First, we’ve eliminated a couple of extraneous variables, responseCode and responseBody. Second, we’ve reduced the number of lines of code a programmer needs to scan to understand which HttpResponse we’re returning from the entire method to a single line.
Rather than tracing the values of responseCode and responseBody from the top of the method through the try block and finally into the HttpResponse, we only need to look at the appropriate piece of the try block to understand the final value of the HttpResponse. These changes combine to give us code that’s more readable and concise.
Now let’s add in the class that ties it all together, TinyWeb. Like its Java counterpart, TinyWeb is instantiated with a map of Controllers and a map of filters. Unlike Java, we don’t define a class for filter; we simply use a list of higher-order functions!
Also like the Java version, the Scala TinyWeb has a single method, handleRequest, which takes in an HttpRequest. Instead of returning an HttpResponse directly, we return an Option[HttpResponse], which gives us a clean way of handling the case when we can’t find a controller for a particular request. The code for the Scala TinyWeb is below:
ScalaExamples/src/main/scala/com/mblinn/mbfpp/oo/tinyweb/stepfour/Tinyweb.scala | |
| package com.mblinn.mbfpp.oo.tinyweb.stepfour |
| class TinyWeb(controllers: Map[String, Controller], |
| filters: List[(HttpRequest) => HttpRequest]) { |
| |
| def handleRequest(httpRequest: HttpRequest): Option[HttpResponse] = { |
| val composedFilter = filters.reverse.reduceLeft( |
| (composed, next) => composed compose next) |
| val filteredRequest = composedFilter(httpRequest) |
| val controllerOption = controllers.get(filteredRequest.path) |
| controllerOption map { controller => controller.handleRequest(filteredRequest) } |
| } |
| } |
Let’s take a look at it in greater detail starting with the class definition.
| class TinyWeb(controllers: Map[String, Controller], |
| filters: List[(HttpRequest) => HttpRequest]) { |
| classBody |
| } |
Here we’re defining a class that takes two constructor arguments, a map of controllers and a list of filters. Note the type of the filters argument, List[(HttpRequest) => HttpRequest]. This says that filters is a list of functions from HttpRequest to HttpRequest.
Next up, let’s look at the signature of the handleRequest method:
| def handleRequest(httpRequest: HttpRequest): Option[HttpResponse] = { |
| functionBody |
| } |
As advertised, we’re returning an Option[HttpResponse] instead of an HttpResponse. The Option type is a container type with two subtypes, Some and None. If we’ve got a value to store in it, we can store it in an instance of Some; otherwise we use None to indicate that we’ve got no real value. We’ll cover Option in greater detail in Pattern 8, Replacing Null Object.
Now that we’ve seen the TinyWeb framework, let’s take a look at it in action. We’ll use the same example from the Java section, returning a list of friendly greetings. However, since it’s Scala, we can poke at our example in the REPL as we go. Let’s get started with our view code.
Let’s take a look at using our Scala TinyWeb framework.
We’ll start by creating a FunctionView and the rendering function we’ll compose into it. The following code creates this function, which we’ll name greetingViewRenderer, and the FunctionView that goes along with it:
ScalaExamples/src/main/scala/com/mblinn/mbfpp/oo/tinyweb/example/Example.scala | |
| def greetingViewRenderer(model: Map[String, List[String]]) = |
| "<h1>Friendly Greetings:%s".format( |
| model |
| getOrElse("greetings", List[String]()) |
| map(renderGreeting) |
| mkString ", ") |
| |
| private def renderGreeting(greeting: String) = |
| "<h2>%s</h2>".format(greeting) |
| |
| def greetingView = new FunctionView(greetingViewRenderer) |
We’re using a couple of new bits of Scala here. First, we introduce the map method, which lets us map a function over all the elements in a sequence and returns a new sequence. Second, we’re using a bit of syntactic sugar that Scala provides that allows us to treat any method with a single argument as an infix operator. The object on the left side of the operator is treated as the receiver of the method call, and the object on the right is the argument.
This bit of syntax means that we can omit the familiar dot syntax when working in Scala. For instance, the two usages of map below are equivalent:
| scala> val greetings = List("Hi!", "Hola", "Aloha") |
| greetings: List[java.lang.String] |
| |
| scala> greetings.map(renderGreeting) |
| res0: List[String] = List(<h2>Hi!</h2>, <h2>Hola</h2>, <h2>Aloha</h2>) |
| |
| scala> greetings map renderGreeting |
| res1: List[String] = List(<h2>Hi!</h2>, <h2>Hola</h2>, <h2>Aloha</h2>) |
Now let’s take a look at our controller code. Here we create the handleGreetingRequest function to pass into our Controller. As a helper, we use makeGreeting, which takes in a name and generates a random friendly greeting.
Inside of handleGreetingRequest we create a list of names by splitting the request body, which returns an array like in Java, converting that array into a Scala list and mapping the makeGreeting method over it. We then use that list as the value for the "greetings" key in our model map:
ScalaExamples/src/main/scala/com/mblinn/mbfpp/oo/tinyweb/example/Example.scala | |
| def handleGreetingRequest(request: HttpRequest) = |
| Map("greetings" -> request.body.split(",").toList.map(makeGreeting)) |
| |
| private def random = new Random() |
| private def greetings = Vector("Hello", "Greetings", "Salutations", "Hola") |
| private def makeGreeting(name: String) = |
| "%s, %s".format(greetings(random.nextInt(greetings.size)), name) |
| |
| def greetingController = new FunctionController(greetingView, handleGreetingRequest) |
Finally, let’s take a look at our logging filter. This function simply writes the path that it finds in the passed-in HttpRequest to the console and then returns the path unmodified:
ScalaExamples/src/main/scala/com/mblinn/mbfpp/oo/tinyweb/example/Example.scala | |
| private def loggingFilter(request: HttpRequest) = { |
| println("In Logging Filter - request for path: %s".format(request.path)) |
| request |
| } |
To finish up the example, we need to create an instance of TinyWeb with the controller, the view, and the filter we defined earlier, and we need to create a test HttpResponse:
ScalaExamples/src/main/scala/com/mblinn/mbfpp/oo/tinyweb/example/Example.scala | |
| def tinyweb = new TinyWeb( |
| Map("/greeting" -> greetingController), |
| List(loggingFilter)) |
| def testHttpRequest = HttpRequest( |
| body="Mike,Joe,John,Steve", |
| path="/greeting") |
We can now run the test request through TinyWeb’s handleRequest method in the REPL and view the corresponding HttpResponse:
| scala> tinyweb.handleRequest(testHttpRequest) |
| In Logging Filter - request for path: /greeting |
| res0: Option[com.mblinn.mbfpp.oo.tinyweb.stepfour.HttpResponse] = |
| Some(HttpResponse(<h1>Friendly Greetings:<h2>Mike</h2>, <h2>Nam</h2>, <h2>John</h2>, |
| 200)) |
That wraps up our Scala version of TinyWeb. We’ve made a few changes to the style that we used in our Java version. First, we replaced most of our iterative code with code that’s more declarative. Second, we’ve replaced our bulky builders with Scala’s case classes, which give us a built-in way to handle immutable data. Finally, we’ve replaced our use of Functional Interface with plain old functions.
Taken together, these small changes save us quite a bit of code and give us a solution that’s shorter and easier to read. Next up, we’ll take a look at TinyWeb in Clojure.
52.15.245.1