Chapter 3
Web Services

Clojure is a great general-purpose language, but it really shines when it comes to web services. Clojure's characteristic focus on immutable data structures, safe concurrency, and code re-use allows you to write services that are composable, reliable, and expressive.

In this chapter, we'll introduce the fundamental abstractions common to virtually all Clojure services: HTTP handlers, middleware, and routing. You'll use the popular Compojure template, which is based on Ring for HTTP abstraction and Compojure for routing. After a tour of the building blocks of a web service, you'll actually write one from start to finish: a storage-backed link shortener. The service will let users submit a full URL and a short name, then redirect requests to that short name back to the original URL. Finally, we'll briefly cover some of the popular options for deploying Clojure web services in development or production, either locally or to the cloud.

PROJECT OVERVIEW

For this example project, you're going to use the standard compojure template:

lein new compojure example-project

The result is something very similar to the default template, but with a few extras:

  1. An added dependency for Compojure, a popular request routing library
  2. A development-only dependency on ring-mock for testing
  3. The lein-ring plugin to easily start a server with lein ring server
  4. Service-oriented example code and tests

At this point, it would be a good idea to take a few minutes to browse through the project, especially the src/example_project/handler.clj and test/example_project/handler_test.clj files, and see if you can examine what's going on. While you're exploring the code, keep the following questions in mind:

  • What URLs will this web service handle, and what will the responses look like?
  • Do you think the tests will pass?
  • How do you think you might add a new endpoint?

Afterward, run lein ring server to start the example service and automatically open a browser (see Figure 3.1):

Snapshot of a dialog box that displays the message “Hello World.”

Figure 3.1

∼/src/chapter3$ lein ring server
2016-01-30 13:43:11.821:INFO:oejs.Server:jetty-7.6.13.v20130916
2016-01-30 13:43:11.853:INFO:oejs.AbstractConnector:Started
  [email protected]:3000
Started server on port 3000

As a bonus challenge, try adding a /film endpoint that recommends your favorite movie, then add a test to make sure it works. Once you're done experimenting, continue on to read about namespace layout in a Clojure web service.

Namespace Layout

Like the default template, the compojure template sets up just one core namespace—example-project.handler. We'll get to handlers in an upcoming section, but let's cover now how to organize a web service into namespaces.

First, take a quick look at the project.clj file, and note the following line:

:ring {:handler example-project.handler/app}

It's important that :handler be associated with a reference to a ring handler. For now, the important thing to remember is that you'll always want to know where your default handler is to provide an accurate reference.

We'll go into more depth about namespace layout when working on the example project later in this chapter, but for the rest of this section think about what kinds of functions might belong in separate namespaces and why.

ELEMENTS OF A WEB SERVICE

Web services in Clojure are constructed differently than services in many other languages. First, the Clojure community has largely come to a consensus that heavy-duty web frameworks are unnecessary at best and harmful at worst. Instead, web services are made out of an ad hoc collection of libraries that handle common tasks like HTTP abstraction, routing, and serialization.

Let's examine some popular options for Clojure web services, focusing on a deep understanding of the fundamental abstractions that these services are built from. By the end of this section, you'll be able to tackle a real world example that makes use of everything you learn here.

Libraries, Not Frameworks

One thing you may notice about Clojure compared to other languages is that the community tends to shy away from heavy duty, opinionated web frameworks like Rails, Django, and Play. Instead, a Clojure project will typically make use of a number of smaller libraries that fill limited roles, like routing, database interactions, and templating.

Decoupling these distinct tasks allows you to combine the libraries that work for you, or substitute your own code at will, and build a project that doesn't require you to bend your logic to fit the assumptions made by the author of a given framework.

This flexibility does, of course, come at a cost. Using, say, four libraries instead of one framework means that you're going to have at least four times as many decisions to make. The still-growing Clojure community is also more fractured than other communities, with fewer experts per library to go around.

It's also possible that you'll run into compatibility issues between libraries, although this rarely happens in practice. In general, the separation of concerns between libraries makes it pretty easy to ensure that they all work together.

HTTP

On some level, a web service is really just a way to turn HTTP requests into HTTP responses. That's a gross oversimplification, but it's also a very helpful thing to remember. You've already seen how Clojure excels at turning input data into useful output data using (mostly) pure functions, so wouldn't it be nice to apply those same techniques to requests and responses?

Well, most Clojure web services use a library called Ring, which lets you treat requests like a normal hash map, with keys for the request method, URI, query string, etc. You write functions that take request maps and return response maps, which have keys for the status, body, and headers. A function from request to response is called a handler, which is an essential concept in Ring.

Ring Requests

Here's an example GET request from guest123 to http://localhost/index.html:

{:server-port 80,
 :server-name "localhost",
 :remote-addr "guest123",
 :uri "/index.html",
 :query-string nil,
 :scheme :http,
 :request-method :get,
 :headers {"host" "localhost"}}

We'll be talking a lot about Ring requests in upcoming sections, but for now the important thing is to get a general idea of what they look like. If you've written web services before, you should see what's going on in the example above. Remember, since this is a normal Clojure map, we can access these fields using the key as a function, such as (:request-method request) would return :get. If you want to get the value of the host header, you can use (get-in request [:headers "host"]), and so on.

Not all requests are that simple. Here's a POST request from localhost to http://localhost/links with a body:

{:remote-addr "localhost",
 :headers
 {"host" "localhost",
  "content-type" "application/x-www-form-urlencoded",
  "content-length" "28"},
 :server-port 80,
 :content-length 28,
 :content-type "application/x-www-form-urlencoded",
 :uri "/links",
 :server-name "localhost",
 :query-string nil,
 :body #object[java.io.ByteArrayInputStream]...,
 :scheme :http,
 :request-method :post}

Most of this example is fairly clear: You have some extra headers relating to the body (as expected), so the :request-method now says :post, the :uri key gives the path to the links endpoint, etc. What may be a bit surprising is that the :body key now points to a java.io.ByteArrayInputStream. This class has some gotchas covered later, in addition to a string and a native data structure.

Ring Responses

Ideally, every request should be met with a response, and Ring represents those with maps as well. Here's a simple Hello World response map:

{:status 200,
 :headers {"Content-Type" "text/html; charset=utf-8"},
           :body "Hello World"}

As you can see, HTTP responses are much simpler than requests—all you have to worry about are the status code, the headers, and the body. For some responses, the body is expected to be blank. Take a typical 302 redirect, such as:

{:status 302,
 :headers {"Location" "http://example.com"},
 :body ""}

Here, the important content is included under the "Location" header, rather than the body. Note that the body is still included, even though it's just the empty string. This is a practice that you should observe in your own responses, and Ring's built-in response helpers will add it for you where appropriate.

Now that you've seen both request and response maps, you can begin to think of your web service as a series of functions from the former to the latter. If you remember nothing else, remember that a function that takes a request and returns a response is called a handler.

Response Helpers

A 404 Not Found response from the example project will look something like this:

{:status 404,
 :headers
 {"Content-Type" "text/html; charset=utf-8",
  "Set-Cookie"
  ("ring-session=855650aa-6209-48af-944f-177cac7fad56;Path=/;HttpOnly"),
   "X-XSS-Protection" "1; mode=block",
   "X-Frame-Options" "SAMEORIGIN",
   "X-Content-Type-Options" "nosniff"},
  :body "Not Found"}

Although it's possible to write a response map by hand, it's usually not done that way. Instead, the process is typically broken down into two main phases:

  1. Create the response using helpers from the ring.util.response namespace.
  2. Apply middleware to outgoing responses to add common headers.

In the case of the compojure template, nearly all the preceding map is created by a combination of compojure.route/not-found and ring.middleware.defaults/wrap-defaults, both of which we'll cover later in this chapter.

For now, though, let's look at how to use the ring.util.response helpers to create a similar map:

(require '[ring.util.response :as ring-response])
(def my404
  (-> (ring-response/response "Not found")
    ;; returns a basic response map with "Not found" in the body
    (ring-response/status 404)
    ;; updates the status of the response to 404
    (ring-response/content-type "text/html")
    ;; adds the Content-Type header to the response
    (ring-response/charset "utf-8")
    ;; extends the Content-Type header to include the character set
    ))

With the notable exception of ring.util.response/response (which takes only a body), most of the helper functions take a response map as their first argument. What this means in practice is that you'll almost always use them with the -> (thread-first) macro, so you can read the whole expression as a series of updates to the immutable response map.

After evaluating the above forms, my404 looks like this:

{:status 404,
 :headers {"Content-Type" "text/html; charset=utf-8"},
 :body "Not found"}

It's still missing a lot of the headers from the template's 404 response, but it's the job of the middleware to handle those.

You should take a moment to browse the ring.util.response namespace once or twice to get an idea of what you can do with Ring responses without having to reinvent the wheel, so to speak. You'll find that most of the functions are short and accomplish one task reliably, which makes them great examples for your own helpers.

Testing Ring Handlers

One of the great things about Ring is how easy it is to test. Recall how one of the added benefits of the compojure template is that it automatically adds a dependency on ring-mock. This section covers how ring-mock is used to test ring handlers without having to start a live HTTP server.

First of all, run lein test in the project directory (if you haven't already) to verify that they do pass. Once you're satisfied, look at how the tests actually work:

(ns chapter3.handler-test
  (:require [clojure.test :refer :all]
            [ring.mock.request :as mock]
            [chapter3.handler :refer :all]))
(deftest test-app
  (testing "main route"
           (let [response (app (mock/request :get "/"))]
             (is (= (:status response) 200))
             (is (= (:body response) "Hello World"))))
  (testing "not-found route"
           (let [response (app (mock/request :get "/invalid"))]
             (is (= (:status response) 404)))))

Notice that app is being called like a function (because handlers are functions), but it's being passed the mock/request thing. Let's see what happens if you evaluate (mock/request :get "/") in a REPL:

{:server-port 80,
 :server-name "localhost",
 :remote-addr "localhost",
 :uri "/",
 :query-string nil,
 :scheme :http,
 :request-method :get,
 :headers {"host" "localhost"}}

It's a Ring request map! Only this one has a lot of the boring stuff filled in for us, which makes the tests much easier to read. Let's continue by evaluating the whole expression (app (mock/request :get "/")):

{:status 200,
 :headers {"Content-Type" "text/html; charset=utf-8"},
           :body "Hello World"}

If you guessed that this is a Ring response map, you're absolutely correct. Once again, you can clearly see that Ring handlers are ordinary Clojure functions that take a (request) map as a parameter and return a (response) map. What's interesting is that these tests are avoiding actual HTTP transport by just calling the handlers directly—there's no network traffic involved.

The ring.mock.request/request function can just as easily generate a :post request, which will be very helpful later when we talk about ByteArrayInputStreams. Try it out with (mock/request :post "/foo" "Hello, this request has a body"):

{:remote-addr "localhost",
 :headers {"host" "localhost", "content-length" "30"},
 :server-port 80,
 :content-length 30,
 :uri "/foo",
 :server-name "localhost",
 :query-string nil,
 :body
 #object[java.io.ByteArrayInputStream 0x6e9da8f2 "java.io.ByteArrayInputStream..."],
 :scheme :http,
 :request-method :post} 

Another thing that's worth remarking on is that there's only one handler for our entire (small) application: app. That's a result of Compojure doing its job, which is to compose handlers for various routes into a single handler that can answer any request it gets. This is covered more in the "Routing" section.

The ring-mock library only includes a few functions, most of which fine-tune the requests generated by ring.mock.request/request by updating headers, adding query parameters, and more. If you find yourself needing to create more sophisticated requests in your tests, have a quick look at the ring.mock.request namespace first to see if it has what you need.

Ring Middleware

Recall that handlers are functions that turn a request into a response, and they are the fundamental concept in Ring. The term middleware refers to a function that takes a handler and returns a handler. Because middleware has the same type for its parameter and return value, you can stack them indefinitely—middleware upon middleware upon middleware, until you get down to a basic handler.

Catching Exceptions

Let's say, for example, that you want to make sure that an uncaught exception in your service doesn't actually crash the whole thing. Instead, you'd probably prefer to return a 500 response and keep trying to service other requests. You could diligently add a try/catch to each individual handler and return the 500 there, but that would result in a lot of needless code duplication.

Using middleware for that purpose, separate that logic and apply it at whatever level in the application you want. Here's a plan for the 500 middleware, which is called wrap-500-catchall:

  1. We take a handler as a parameter.
  2. We're returning a new handler, so we'll need to start with something like (fn [request] ...).
  3. At this point, both the handler and the request are in scope, so we could just call (handler request).
  4. You want to wrap that in a try/catch expression.
  5. The catch body should always return a 500 response and otherwise swallow the exception (although you would normally log it as well).

Middleware functions are usually named wrap-x, because each one is a sort of transparent layer around a handler. You can keep wrapping a handler with middleware indefinitely, and it's not uncommon to see ten or more layers, although that can be pretty unwieldy!

Here's one possible implementation of the wrap-500-catchall middleware:

(defn wrap-500-catchall
  "Wrap the given handler in a try/catch expression, returning a 500 response if
  any exceptions are caught."
  [handler] ;; we want to take a handler as a parameter
  (fn [request] ;; we're also returning a handler
    (try (handler request)
      ;; middleware almost always calls the handler that's passed in
      (catch Exception e
        (-> (ring-response/response (.getMessage e))
          ;; place the exception's message in the body
          (ring-response/status 500)
          ;; set the status code to 500
          (ring-response/content-type "text/plain")
          ;; there's no HTML here, so we'll use text/plain
          (ring-response/charset "utf-8")
          ;; and update the character set
          )))))

Let's add a new route that throws an exception for us. Routing is covered in more detail in the next section, but now you can get something working with just a single line in the app-routes definition in handler.clj:

(defroutes app-routes
  (GET "/" [] "Hello World")
  (GET "/trouble" [] (/ 1 0)) ;; this won't end well!
  (route/not-found "Not Found"))

Add the definition of wrap-500-catchall directly below app-routes, then use it to wrap app:

(def app
  (wrap-500-catchall (wrap-defaults app-routes site-defaults)))

This works because app is a handler, and all middleware takes a handler and returns a handler.

Let's verify that the middleware does what you might expect by writing a test.

(deftest catchall-test
  (testing "when a handler throws an exception"
    (let [response (app (mock/request :get "/trouble"))]
      (testing "the status code is 500"
        (is (= 500 (:status response))))
      (testing "and the body only contains the exception message"
        (is (= "Divide by zero" (:body response)))))))

If you want to see this for yourself in your browser, kill the Ring server process (if it's running) and run lein ring server again. Now, when you visit http://localhost:3000/trouble, you should see a "Divide by zero" message, while the other routes continue to work as usual.

Converting Request Bodies

The wrap-500-catchall middleware only affects the response, but you can also use middleware to prepare the request before the handlers even see it. Remember the Ring Request section above, where you saw that the :body key contains a java.io.ByteArrayInputStream? Well, run lein repl from the project directory so you can investigate that class a little bit:

user=> (require '[ring.mock.request :as mock])
nil
user=> (def request-with-body (mock/request :post "/" "This is the request body"))
#'user/request-with-body
user=> (:body request-with-body)
#object[java.io.ByteArrayInputStream 0x71e6fe51 "java.io.ByteArrayInputStream@..."]

At this point you can see that the body is a java.io.ByteArrayInputStream, but you can't see what its contents are. For that, you can call Clojure's slurp function.

user=> (slurp (:body request-with-body))
"This is the request body"

Slurping the body does indeed return the string. But what if you slurp it again?

user=> (slurp (:body request-with-body))
""

That is disastrous. Slurping a ByteArrayInputStream also empties its contents. It turns out that, unlike almost everything else you deal with in Clojure, java.io.ByteArrayInputStream objects are mutable—simply reading their contents as a string leaves them blank. You can carefully work around that fact every time you process a request with a body, or you can write a new middleware so that you don't have to worry about it. Here's a plan:

  1. It's middleware, thus taking a handler as a parameter.
  2. Start with (fn [request] ...) for the return value.
  3. If the request doesn't have a body, call (handler request).
  4. Otherwise, instead of just calling (handler request), you will replace the request's :body with the slurped version, eliminating the need to ever worry about ByteArrayInputStreams in the rest of the application.
  5. Finally, call the handler with the prepared version of the request.

Here's a basic implementation of the middleware, which you can call wrap-slurp-body:

(defn wrap-slurp-body
  [handler]
  (fn [request]
    (if (instance? java.io.InputStream (:body request))
      (let [prepared-request (update request :body slurp)]
        (handler prepared-request))
      (handler request))))

Note that you're checking for instances of java.io.InputStream, a superclass of java.io.ByteArrayInputStream. Although you've only seen ByteArrayInputStreams so far, the fact is that Ring will sometimes pass other subclasses of InputStream, but they can all be read with slurp and suffer from the same mutability issue, so this is a much safer approach.

Normally you would use a route that takes a body in order to test this, but it's not quite time to do that with Compojure (that's coming up in the next section). That said, there's nothing stopping you from writing a simpler handler and testing it. Let's write a handler that just echoes the body back:

(defn body-echo-handler
  [request]
  (if-let [body (:body request)] ;; remember, not all requests have bodies!
    (-> (ring-response/response body)
      (ring-response/content-type "text/plain")
      (ring-response/charset "utf-8"))
    ;; if there's no body, let's call this a 400 (Bad request)
    (-> (ring-response/response "You must submit a body with your request!")
      (ring-response/status 400))))

Now let's wrap the handler in some middleware to give you something to use for tests:

(def body-echo-app
(-> body-echo-handler
  wrap-500-catchall
  wrap-slurp-body))

And finally, add the tests themselves to handler_test.clj:

(deftest slurp-body-test
  (testing "when a handler requires a request body"
    (testing "and a body is provided"
      (let [response (body-echo-app (mock/request :post "/" "Echo!"))]
        (testing "the status code is 200"
          (is (= 200 (:status response)))
          (testing "with the request body in the response body"
            (is (= "Echo!" (:body response)))))))
    (testing "and a body is not provided"
      (let [response (body-echo-app (mock/request :get "/"))]
        (testing "the status code is 400"
          (is (= 400 (:status response))))))))

Even though you're not using it yet, you might as well wrap the main app in wrap-slurp-body as well. At the same time, let's clean it up a bit with the thread-first macro:

(def app
  (-> app-routes
    (wrap-defaults api-defaults)
    wrap-slurp-body
    wrap-500-catchall))

It can be a pain, but you often have to pay attention to the order in which you apply middleware. As each middleware function wraps the handler, that new middleware becomes the first thing to see the request. So, even though it's last in the list, wrap-500-catchall is the first thing to get called when a request is being answered. Let's consider a slightly different order for a moment:

(def app
  (-> app-routes
    wrap-500-catchall
    (wrap-defaults api-defaults)
    wrap-slurp-body))

In this case, the wrap-500-catchall middleware only applies to app-routes directly—it will never see exceptions thrown by wrap-defaults or wrap-slurp-body. That may or may not be what you would want in your own application, but the important thing is to be aware of the effect that ordering can have on your middleware.

Middleware is a powerful abstraction around both requests and responses. When your middleware is doing its job, your handlers can be smaller and more focused, which is key to writing clean, maintainable web services. It's easy to compose middleware by stacking it on top of your handlers, but remember that the last middleware you apply will be the first to be called when there's a request.

Routing

Imagine a simple web service with two endpoints: a /links endpoint that responds to GET and POST requests, and a /links/{ID} endpoint, which may support GET and DELETE. The routing library is responsible for examining an incoming request and passing it along (with any relevant data) to a function of your choosing.

If you look again at the request maps in the HTTP section, you can probably begin to imagine how this would work—examine the :request-method and :uri keys in the request map, and if they match an endpoint in your application then pass the request map along to the appropriate function to return a response.

You could do that manually, but hardly anybody does. Instead, by offloading that work to a routing library, you will keep your code much cleaner and easier to reason about. By far, the most popular Clojure routing library is Compojure. It's well documented, fairly straightforward, and very flexible.

Composing Routes

The chapter3.handler namespace already has three Compojure routes defined under app-routes:

(defroutes app-routes
  (GET "/" [] "Hello World")
  (GET "/trouble" [] (/ 1 0))
  (route/not-found "Not Found"))

The defroutes macro is purely a convenience around def (you've seen) and routes ( will be covered in depth soon). Just to prove there's no other magic involved, let's revise this bit of code to eliminate the use of defroutes:

(def app-routes
  (routes
   (GET "/" [] "Hello World")
   (GET "/trouble" [] (/ 1 0))
   (route/not-found "Not Found")))

Now that the structure is easier to see, let's talk about what's going on here. Although it might not look much like it right now, remember that app-routes is a handler. That is, app-routes takes a Ring request map and returns a response map.

That fact alone gives you a good idea of what routes does: It takes some number of Compojure routes and turns them into a single handler. But what are the routes individually? Have a guess, then open up a REPL in the project directory:

user=> (require '[compojure.core :refer :all])
nil
user=> (def get-foo (GET "/foo" [] "Hello from foo"))
#'user/get-foo
user=> (require '[ring.mock.request :as mock])
nil
user=> (get-foo (mock/request :get "/foo"))
{:status 200,
 :headers {"Content-Type" "text/html; charset=utf-8"},
 :body "Hello from foo"}

As you can see, GET (and indeed, the PUT/POST/DELETE/etc.) returns a handler. In this case, you'll test it out with a request that the handler was ready for—a simple GET request to /foo. Let's jump back to the REPL and see what happens if the route doesn't match:

user=> (get-foo (mock/request :get "/"))
nil

Well, nil isn't a valid Ring response, so it would be a really bad idea to use this handler directly. Let's look again at app-routes:

(def app-routes
  (routes
   (GET "/" [] "Hello World")
   (GET "/trouble" [] (/ 1 0)) ;; this won't end well!
   (route/not-found "Not Found")))

Now that you know that each route is a handler that can return nil, you can fully deduce the purpose of the routes function: to combine multiple handlers, sending the request to each one until a valid response (not nil) is returned.

This is the first function that can turn two or more handlers into one, and it's about time! You don't want to write one huge handler all at once to represent the application; it's much more reasonable to split it up and handle one route at a time.

Writing Routes

Here again are some simple Compojure routes:

(defroutes app-routes
  (GET "/" [] "Hello World")
  (GET "/trouble" [] (/ 1 0)) ;; this won't end well!
  (route/not-found "Not Found"))

There's definitely a common structure here:

  • HTTP verb, like GET, PUT, POST, DELETE
  • A path
  • A parameter vector
  • The body of the response

Most of the time, Compojure routes resemble Clojure functions. Instead of defn, each route is introduced with the HTTP verb it responds to. Standing in for the function name is the path, which can contain abstract segments such as :id. Like an ordinary function, a route must have a (possibly empty) parameter vector, which supports the standard destructuring forms. Finally, you can define the result of matching the route by providing an expression to evaluate.

The routes above are extremely simple, which is good when trying not to dive too deep into routing, but now it's time to jump in with both feet. Let's bring the echo handler into Compojure:

(defroutes app-routes
  (GET "/" [] "Hello World")
  (GET "/trouble" [] (/ 1 0))
  (POST "/echo" [:as request] (body-echo-handler request))
  (route/not-found "Not Found"))

This finally gives you something more interesting to look at in the parameter vector, so let's focus on that first. You may recognize :as, which is part of Clojure's standard destructuring syntax. Normally, :as x would take all of the parameters and wrap them up in a collection called x. In Compojure specifically, :as is a nice shortcut to get the entire Ring request as a parameter so that it can be passed off to a handler. The request can have any name, but in this case it's hard to imagine a reason not to call it request. Once you have the request in scope, call the old body-echo-handler directly.

Let's revisit the tests for app-routes and add a couple for the newly integrated echo endpoint:

(deftest test-app
  (testing "main route"
    (let [response (app (mock/request :get "/"))]
      (is (= (:status response) 200))
      (is (= (:body response) "Hello World"))))
  (testing "echo route"
    (let [response (app (mock/request :post "/echo" "Echo!"))]
      (is (= 200 (:status response)))
      (is (= "Echo!" (:body response))))
    (let [response (app (mock/request :post "/echo"))]
      (is (= 400 (:status response)))))
  (testing "not-found route"
    (let [response (app (mock/request :get "/invalid"))]
      (is (= (:status response) 404)))))

This works for POST, but this handler is used to accepting any request that contains a body. You can work your way back up to the old functionality like this:

(defroutes app-routes
  (GET "/" [] "Hello World")
  (GET "/trouble" [] (/ 1 0))
  (POST "/echo" [:as request] (body-echo-handler request))
  (PUT "/echo" [:as request] (body-echo-handler request))
  (DELETE "/echo" [:as request] (body-echo-handler request))
  (route/not-found "Not Found"))

Or maybe this doesn't work. This amount of code duplication is unforgivable, even in this small example project. Let's take advantage of another routing function that Compojure provides:

(defroutes app-routes
  (GET "/" [] "Hello World")
  (GET "/trouble" [] (/ 1 0)) ;; this won't end well!
  (ANY "/echo" [:as request] (body-echo-handler request))
  (route/not-found "Not Found"))

Now you can match any HTTP request to the /echo path. Let's verify that by adding a few more requests to the tests for the echo route:

(deftest test-app
  (testing "main route"
    (let [response (app (mock/request :get "/"))]
      (is (= (:status response) 200))
      (is (= (:body response) "Hello World"))))
  (testing "echo route"
    (let [response (app (mock/request :post "/echo" "Echo!"))]
      (is (= 200 (:status response)))
      (is (= "Echo!" (:body response))))
    (let [response (app (mock/request :put "/echo" "Hello!"))]
      (is (= 200 (:status response)))
      (is (= "Hello!" (:body response))))
    (let [response (app (mock/request :patch "/echo" "Goodbye!"))]
      (is (= 200 (:status response)))
      (is (= "Goodbye!" (:body response))))
    (let [response (app (mock/request :post "/echo"))]
      (is (= 400 (:status response)))))
  (testing "not-found route"
    (let [response (app (mock/request :get "/invalid"))]
      (is (= (:status response) 404)))))

There's more to do here to clean up the echo handler. Although convenient, it's usually unnecessary to pass along the entire request to a Compojure route handler. In this case, all the echo handler actually needs is the body, which may be nil. Leave echo-body-handler in place, but write a new function that doesn't rely on any extraneous data:

(defn echo ;; based on echo-body-handler
  [body]
  (if (not-empty body) ;; excludes nil and the empty string
    (-> (ring-response/response body)
      (ring-response/content-type "text/plain")
      (ring-response/charset "utf-8"))
    ;; if there's no body, let's call this a 400 Malformed
    (-> (ring-response/response "You must submit a body with your request!")
      (ring-response/status 400))))

Notice how the function is renamed to echo. Since it no longer takes a full request map, it would be misleading to give it a name with handler in it. Also, since it only takes the body as a parameter, it doesn't make sense to also put body in the name. So that leaves you with echo. Unfortunately, the tests are also failing now, so you need to update app-routes to only pass the body along to this function:

(defroutes app-routes
  (GET "/" [] "Hello World")
  (GET "/trouble" [] (/ 1 0)) ;; this won't end well!
  (ANY "/echo" [:as {body :body}] (echo body))
  (route/not-found "Not Found"))

Notice how the request was replaced with {body :body}. The latter is also standard destructuring syntax, roughly translating to "take whatever's in the :body key and bind it to body." Leave the :as there to make sure that you're destructuring the request map; otherwise you'll get an error about an unexpected binding form. Go ahead and run the tests again to make sure everything's back in working order with the newly refactored handler.

Compojure Responses

You may have noticed that Compojure doesn't seem to mind if a string is returned instead of a proper ring response map. This is not practical most of the time because you need to add a bit more with the request. Still, it's worth taking note of exactly how Compojure is able to turn that string into a Ring response, since you can extend that behavior to other types that might be more useful.

Compojure uses the compojure.response/Renderable protocol for this purpose. The protocol defines a single method, render, which returns a proper Ring response. You can take a look at the compojure.response namespace to see how this method is defined for nil, strings, maps, etc.

Path Variables

At the beginning of this section, a route is mentioned with an abstract segment: /links/{ID}, where {ID} is an extra parameter that the handler can use. Compojure also makes this quite easy with a little bit of magic:

(defroutes app-routes
  (GET "/" [] "Hello World")
  (GET "/trouble" [] (/ 1 0))
  (GET "/links/:id" [id] (str "The id is: " id))
  (ANY "/echo" [:as {body :body}] (echo body))
  (route/not-found "Not Found"))

Here you have a new route, /links/:id, with just [id] for the parameter vector. When Compojure matches a route, any segments that start with a colon are treated as abstract path segments and added to the request map. You can access them through the request yourself, but Compojure also adds a more convenient way—providing the name of the segment in the parameter vector. That means that you can't use, say, [link-id] for the parameter name; it wouldn't have matched.

Let's write some tests to verify that the /links/:id endpoint actually works:

(deftest links-test
  (testing "the links/:id endpoint"
    (testing "when an id is provided"
      (let [response (app (mock/request :get "/links/foo123"))]
        (testing "returns a 200"
          (is (= 200 (:status response)))
          (testing "with the id in the body"
            (is (re-find #"foo123" (:body response)))))))
    (testing "when the id is omitted"
      (let [response (app (mock/request :get "/links"))]
        (testing "returns a 404"
          (is (= 404 (:status response))))))
    (testing "when the path is too long"
      (let [response (app (mock/request :get "/links/foo123/extra-segment"))]
        (testing "returns a 404"
          (is (= 404 (:status response))))))))

As the tests show, the route will only match if there is exactly one segment for :id—anything else will result in a 404.

Routes and Middleware

Using middleware to your advantage is always a good idea, but you don't always want to apply the same middleware to every route. For example, you don't actually need to apply the wrap-slurp-body middleware to all of the routes just because the /echo endpoint relies on it. Instead, you can group routes and apply middleware in multiple stages. First, split off the /echo route from the rest of app-routes:

(def body-routes
  (-> (routes
       (ANY "/echo" [:as {body :body}] (echo body)))
    (wrap-routes wrap-slurp-body)))
(defroutes non-body-routes
  (GET "/" [] "Hello World")
  (GET "/trouble" [] (/ 1 0)) ;; this won't end well!
  (GET "/links/:id" [id] (str "The id is: " id))
  (route/not-found "Not Found"))

Note that we're wrapping body-routes directly, rather than at a later stage. This works well conceptually, since any route that gets grouped under body-routes is going to need this middleware and other routes probably won't. This pattern isn't possible with the defroutes macro because there's no way to sneak in the middleware between def and routes in that case.

The other very important note here is that even though you can call wrap-slurp-body on the route, you'll instead pass it as an argument to wrap-routes. The rationale for this has to do with the way that Compojure combines handlers. When you pass multiple handlers to routes, they each get called with the request map until one of them returns a response. If one of those handlers somehow mutates the request map (which wrap-slurp-body does, because it consumes the original body), then subsequent handlers will see the mutated version.

The wrap-routes function is an essential workaround for this problem. It ensures that the given middleware is only applied when the route actually matches, meaning that it will only be called once per request. You should use wrap-routes whenever you're applying middleware with the routes function.

Moving on, instead of wrapping only app-routes in the remaining middleware, you can combine body-routes with app-routes. First, combine body-routes and app-routes into a single handler with the routes function:

(def app-routes
  (routes body-routes non-body-routes))

Next, remove wrap-slurp-body from the app, since it has already been applied to the routes that require it:

(def app
  (-> app-routes
    wrap-500-catchall
    (wrap-defaults api-defaults)))

It's very rare that all of your routes will need exactly the same middleware in exactly the same order, so always think about that when grouping routes together. You can always wrap handlers in middleware and you can always group handlers with routes, so you'll often see a sort of group-wrap-group-wrap-group-wrap pattern that slowly combines handlers into a single application.

JSON Endpoints

Most web services are going to produce and/or consume serialized data structures. Since JSON is by far the most common serialization format for modern web services, it is used in this chapter. Nonetheless, you can easily substitute XML (even on a per-request basis) or something more experimental like Cognitect's new Transit.

Although there's an official Clojure JSON library (clojure.data.json), you'll be using the much more popular Cheshire, which is more flexible and performant. Cheshire allows you to define JSON serialization methods for your own custom data types and it supports streaming, lazy decoding, and pretty-printed JSON output.

Let's add Cheshire as a dependency to the project so we can try it out. Update the :dependencies section in project.clj like so:

:dependencies [[org.clojure/clojure "1.7.0"]
               [compojure "1.4.0"]
               [ring/ring-defaults "0.1.5"]
               [cheshire "5.5.0"]] ;; new line

Then require it in handler.clj:

(ns chapter3.handler
  (:require [compojure.core :refer :all]
            [ring.util.response :as ring-response]
            [compojure.route :as route]
            [cheshire.core :as json] ;; new line
            [ring.middleware.defaults :refer [wrap-defaults site-defaults
              api-defaults]]))

And also require it in handler_test.clj:

(ns chapter3.handler-test
  (:require [clojure.test :refer :all]
            [ring.mock.request :as mock]
            [cheshire.core :as json] ;; new line
            [chapter3.handler :refer :all]))

Accepting JSON Data

Now that we're equipped to deal with JSON, let's write some middleware to handle parsing a JSON-encoded body, thus returning a 400 response if it's malformed:

(defn wrap-json
  [handler]
  (fn [request]
    (if-let [prepd-request (try (update request :body json/decode)
                             (catch com.fasterxml.jackson.core.JsonParseException e
                               nil))]
      (handler prepd-request)
      ;; we'll only get here if there's a parse error
      (-> (ring-response/response "Sorry, that's not JSON.")
        (ring-response/status 400)))))

One thing to note is that this middleware assumes that the body is going to be a string, which means it'll have to be applied before wrap-slurp-body (remember, applied first means called second). Let's write a handler that turns a JSON-encoded body into a Clojure map:

(defn handle-clojurefy
  [request]
  (-> (:body request) ;; extract the body of the request
    str ;; turn it into a string
    ring-response/response ;; wrap it in a response map
    (ring-response/content-type "application/edn"))) ;; set the Content-Type

Note the proper Ring handler is written here, rather than taking any of the shortcuts that Compojure opens up. This is usually the right thing to do—full Ring handlers are easier to test independently and they tend to be more explicit. In this case, returning a response map makes it possible to set the Content-Type to application/edn. If you're not familiar with EDN (Extensible Data Notation), it's essentially Clojure's built-in object serialization standard, and it can be a convenient way to pass data between Clojure services over HTTP.

Let's add the /clojurefy endpoint to body-routes, since it does require a body. You will wrap it with the wrap-json middleware right in the route definition:

(def body-routes
  (-> (routes
       (ANY "/echo" [:as {body :body}] (echo body))
       (POST "/clojurefy" [] (wrap-json handle-clojurefy)))
    wrap-slurp-body))

Remember the compojure.response/Renderable protocol? Well, it turns out that you can use it to skip the whole [:as request] bit. When you use a full-fledged Ring handler for the body of a route, Compojure assumes it's a handler and automatically passes it the request map. It looks a little strange, but it works, and the alternative looks even stranger:

(POST "/clojurefy" [:as request] ((wrap-json handle-clojurefy) request)) ;; weird!

Also note that wrap-json is placed where it can only see requests that have matched the /clojurefy route. This way it won't affect the /echo endpoint, which really doesn't care if the request contains valid JSON.

Finally, let's write some tests to see if this is successful:

(deftest json-test
  (testing "the /clojurefy endpoint"
    (testing "when provided with some valid JSON"
      (let [example-map {"hello" "json"}
            example-json (json/encode example-map)
            response (app (-> (mock/request :post "/clojurefy" example-json)
                            ;; note, we must set the content type of the request
                            (mock/content-type "application/json")))]
        (testing "returns a 200"
          (is (= 200 (:status response)))
          (testing "with a Clojure map in the body"
            (is (= (str example-map) (:body response)))))))
    (testing "when provided with invalid JSON"
      (let [response (app (-> (mock/request :post "/clojurefy" ";!:")
                            (mock/content-type "application/json")))]
        (testing "returns a 400"
          (is (= 400 (:status response))))))))

The tests pass, so all is good! Let's move on to returning JSON responses.

Returning JSON Data

Returning JSON is a bit easier than accepting it. You don't have to worry about problems with parsing; you just need to set the content type and encode the body. Here's a simple middleware function that should take care of this:

(defn wrap-json-response
  [handler]
  (fn [request]
    (-> (handler request) ;; call the handler first
      (update :body json/encode) ;; then encode the body
      (ring-response/content-type "application/json")))) ;; and set Content-Type

Most of the middleware written for this book so far alters incoming requests, so this is a little bit different. Instead of calling the handler last, you call it first. For the rest of the thread-first form let's work with the response map. Thread the response through update to encode the body, then use ring.util.response/content-type to apply the correct Content-Type header.

Now, let's write a handler that returns a JSON object with some basic information about the system it's running on:

(def handle-info
  (wrap-json-response
   (fn [_] ;; we don't actually need the request
     (-> {"Java Version" (System/getProperty "java.version")
          "OS Name" (System/getProperty "os.name")
          "OS Version" (System/getProperty "os.version")}
       ring-response/response))))

This is yet another way to wrap a handler. Since there is only one handler that returns JSON, there's no real problem with wrapping it this closely. If you had a few more routes like this, you'd want to compose them and then wrap the combined handler.

This handler uses the System/getProperty method to fetch some details about the host that the service is running on. You may not want to expose this information generally, but it's a useful thing to know about and a perfectly good source of structured data to encode.

Now let's add a route for the /info endpoint:

(defroutes non-body-routes
  (GET "/" [] "Hello World")
  (GET "/trouble" [] (/ 1 0)) ;; this won't end well!
  (GET "/links/:id" [id] (str "The id is: " id))
  (GET "/info" [] handle-info)
  (route/not-found "Not Found"))

In this case, it will be more interesting to run lein ring server and check out http://localhost:3000/info, but let's write some tests anyway:

(deftest json-response-test
  (testing "the /info endpoint"
    (let [response (app (mock/request :get "/info"))]
      (testing "returns a 200"
        (is (= 200 (:status response))))
      (testing "with a valid JSON body"
        (let [info (json/decode (:body response))]
          (testing "containing the expected keys"
            (is (= #{"Java Version" "OS Name" "OS Version"}
                   (set (keys info))))))))))

One thing to note here is that the keys function makes no guarantees about the order that the keys are in, so you'll use a set comparison to make sure that you don't need any such guarantees. As long as the map has exactly the keys "Java Version," "OS Name," and "OS Version" in any order, the test will pass.

Using ring-json

Writing your own JSON middleware is great practice, but the fact is that this has already been done for you. The ring-clojure organization maintains a very helpful library called ring-json (https://github.com/ring-clojure/ring-json). This library already provides a default 400 response, as well as middleware for JSON requests and JSON responses.

So, in the interest of not reinventing the wheel, let's refactor the code to use the ring-json library. First, add it as a dependency to project.clj:

:dependencies [[org.clojure/clojure "1.7.0"]
               [compojure "1.4.0"]
               [ring/ring-defaults "0.1.5"]
               [ring/ring-json "0.4.0"] ;; add this
               [cheshire "5.5.0"]]

Then require ring.middleware.json in handler.clj:

(ns chapter3.handler
  (:require [compojure.core :refer :all]
            [ring.middleware.json :as ring-json] ;; add this
            [ring.util.response :as ring-response]
            [ring.util.request :as ring-request]
            [compojure.route :as route]
            [cheshire.core :as json]
            [ring.middleware.defaults :refer [wrap-defaults api-defaults]]))

We have a slightly different middleware situation right now because ring-json's version of wrap-json-body stringifies the request body for us; making wrap-slurp-body obsolete for JSON routes. Let's go ahead and move the /clojurefy endpoint out of body-routes and into its own json-routes var while you replace wrap-json with ring-json/wrap-json-body:

(def json-routes
  (routes
   (POST "/clojurefy" [] (ring-json/wrap-json-body handle-clojurefy))))
(def body-routes
  (-> (routes
       (ANY "/echo" [:as {body :body}] (echo body)))
    (wrap-routes wrap-slurp-body)))

You'll also need to update app-routes to include json-routes:

(def app-routes
  (routes json-routes body-routes non-body-routes))

Next, you'll replace wrap-json-response middleware with ring-json/wrap-json-response:

(def handle-info
  (ring-json/wrap-json-response
   (fn [_] ;; we don't actually need the request
     (-> {"Java Version" (System/getProperty "java.version")
          "OS Name" (System/getProperty "os.name")
          "OS Version" (System/getProperty "os.version")}
       ring-response/response))))

Once those changes are made, you can delete both of the hand-crafted JSON middleware functions and re-run the tests. The functionality hasn't changed in any particularly significant way, so they all pass.

JSON endpoints need not be especially daunting, provided you're smart about the abstractions that are available. Use middleware for parsing and encoding, but be very careful about applying it only where necessary and always think twice about the order in which middleware is applied. Avoid writing your own middleware if there's already a good open source solution for it. Although this section focused on JSON, you can easily apply the exact same principles to any other form of serialization.

EXAMPLE SERVICE

In this section, you'll be writing a link shortening service. In the process, you'll see how a CRUD (Create Read Update Destroy) app can work in Clojure. Your service will support the following endpoints:

  • POST /links—Submit a URL and receive a shortened URL.
  • GET /links/:id—Redirect to the URL associated with the ID.
  • PUT /links/:id—Update the URL associated with the ID.
  • DELETE /links/:id—Delete the shortened link.
  • GET /links—Retrieve a listing of all IDs and their associated URLs in JSON format.

This project is a great way to illustrate main elements of every Clojure web service, including storage and testing. By the end of this chapter, you'll also have a great starting point for experimenting with more advanced features like authentication and database integration.

Create the Project

If you were following along with the earlier sections, that's great! We're still starting over, though. Set that project aside and start a new one with this familiar command:

lein new compojure ch3shortener

Have a look around in the project to familiarize yourself with what's available. There is only one namespace right now, and that's ch3shortener.handler. The next section lays out the project deliberately to make the namespaces clean and focused.

Additional Namespaces

The overview section used a single-namespace approach, because you were just trying a few things out and getting familiar with the concepts involved in Clojure web services. But it's time to start taking things more seriously, and that means separating our project into well-defined namespaces.

There are two main concerns when you're planning namespace layout: separation and dependencies. Separation is about keeping namespaces focused and keeping functions and data where you expect to find them. It doesn't sound complicated, but it often requires a lot of thought.

But you can't always just separate namespaces based on what they do. The layout has to also reflect the relationships between different pieces of code. Whenever one namespace requires, uses, or imports another, it creates a dependency relationship. Let's look at the namespace declaration for ch3shortener.handler:

(ns ch3shortener.handler
  (:require [compojure.core :refer :all]
            [compojure.route :as route]
            [ring.middleware.defaults :refer [wrap-defaults site-defaults]]))

This namespace depends on three others: compojure.core, compojure.route, and ring.middleware.defaults. We know that none of those namespaces depend on this one, but what if one of them did? Well, Clojure doesn't allow that sort of thing. If namespace A requires namespace B, then B can't also require A. That type of situation is called a cyclic dependency, and avoiding it is one of the main reasons to break namespaces up.

You've already seen how a web service is generally composed of handlers, routes, and middleware. The three concerns are pretty conceptually distinct, so that alone already suggests that they should be in different namespaces.

What clinches it, though, is the fact that they don't have any mutual dependencies. Think about it this way:

  • Middleware shouldn't depend on handlers or routes.
  • Handlers depend on middleware, but not routes.
  • Routes depend on handlers, and maybe middleware.

So you're unlikely to get in a situation where your handlers namespace requires the middleware namespace and vice versa. Since the three roles are well defined and don't have cyclic dependencies, you should almost certainly maintain them in separate namespaces.

You're also going to create an application namespace to serve as the new entry point for our service. This namespace will be responsible for handling configuration, initializing resources, and returning a master handler that contains all of the routes you want to serve.

Middleware

You're undoubtedly going to need some middleware for this project, so let's create a new file at src/ch3shortener/middleware.clj with these contents:

(ns ch3shortener.middleware)

It's not much to look at for now, but it's a start. You should also be prepared to write middleware tests, so create another file attest/ch3shortener/middleware_test.clj with these contents:

(ns ch3shortener.middleware-test
  (:require [ch3shortener.middleware :refer :all]
            [clojure.test :refer :all]))

Even though you haven't done it yet, you can test middleware independently. Doing so helps you figure out whether a given bug is caused by the middleware itself, middleware ordering, or the handler. You'll have a look at that strategy later on when there is some middleware to test.

Routes

The routes namespace should look like a high-level overview of the endpoints your service makes available. It's important to have a good place to start when you're looking to add or update an existing route. Let's create the routes' namespace at src/ch3shortener/routes.clj and copy over the routes themselves from handler.clj:

(ns ch3shortener.routes
  (:require [ch3shortener.handler :as handler]
            [ch3shortener.middleware :as mw]))
(defroutes app-routes
  (GET "/" [] "Hello World")
  (route/not-found "Not Found"))

You won't have independent tests for routes, so this is all you need for now.

Application

The application namespace is about to unseat ch3shortener.handler as the main point of entry for our service. Create the file src/ch3shortener/application.clj with the following contents:

(ns ch3shortener.application
  (:require [ring.middleware.defaults :refer [wrap-defaults site-defaults]]
            [ch3shortener.routes :as routes]))
(def app
  (wrap-defaults routes/app-routes site-defaults))

This should look familiar, since it's basically just the rest of the ch3shortener.handler namespace with the added dependency on ch3shortener.routes. Similarly, let's create test/ch3shortener/application_test.clj and move the existing handler tests there:

(ns ch3shortener.application-test
  (:require [ch3shortener.application :refer :all]
            [clojure.test :refer :all]
            [ring.mock.request :as mock]))
(deftest test-app
  (testing "main route"
    (let [response (app (mock/request :get "/"))]
      (is (= (:status response) 200))
      (is (= (:body response) "Hello World"))))
  (testing "not-found route"
    (let [response (app (mock/request :get "/invalid"))]
      (is (= (:status response) 404)))))

The last thing you need to do to make ch3shortener.application/app the main handler for your service is to update the :ring section in project.clj:

  :ring {:handler ch3shortener.application/app}

This configuration option tells Ring which handler to use when starting a server with lein ring server. Once you update the setting, try that command out and verify that everything still works. You should see exactly the same behavior as before, only with better namespaces!

Handlers

Well, you should have just about gutted ch3shortener.handler by now. If you've copied out (but not deleted) app-routes and app, it's time to get rid of them. Here's what handler.clj looks like now.

(ns ch3shortener.handler
  (:require [ring.util.request :as req]
            [ring.util.response :as res]))

You need to be prepared to deal with requests and responses, but there is nothing to do just yet. That's fine, since there will be plenty to do here later. Let's also make sure that handler_test.clj is similarly empty:

(ns ch3shortener.handler-test
  (:require [clojure.test :refer :all]
            [ring.mock.request :as mock]
            [ch3shortener.handler :refer :all]))

Default Middleware

Let's take a look at the definition of app in ch3shortener.application:

(def app
  (wrap-defaults routes/app-routes site-defaults))

Ring comes with ring.middleware.defaults/wrap-defaults, which is some general-purpose middleware that sets your service up to behave pretty reasonably out of the box. The second argument there, site-defaults, is a configuration map that tells wrap-defaults that you're creating a website, and turns on the following options (among others):

  • Anti-forgery tokens
  • Session support
  • Cross-site scripting protection
  • Static resource serving

These are meant to represent the best practices for browser-based sites, but they don't apply to the service you're writing now. You don't want to have to worry about anti-forgery tokens and you won't have any static assets to serve.

Luckily, you can just swap in a set of options that better suits your own needs. Just change site-defaults to api-defaults in both places as it appears:

(ns ch3shortener.application
  (:require [ring.middleware.defaults :refer [wrap-defaults api-defaults]]
            [ch3shortener.routes :as routes]))
(def app
  (wrap-defaults routes/app-routes api-defaults))

Using the api-defaults saves a bit of work here and there by automatically setting the content type and charset, without getting bogged down in the extra stuff you don't need from site-defaults. It's a good balance, but you can upgrade to secure-api-defaults if you want to use SSL.

Configuring Ring's default middleware is one of the first really important decisions you can make in your application, and it's worth taking some time to get it right. The built-in configurations cover several common use cases, but don't be afraid to modify them yourself—they're just Clojure maps.

The Storage Protocol

It's time to introduce state. The service is going to need to store and retrieve data, specifically to associate IDs with full URLs. This section contains the creation, implementation, and testing of a fairly typical storage protocol.

Why a Protocol?

Clojure's protocol system is based on, but not identical with, Java's interfaces. Protocols define a set of methods that can have different implementations for different types of data. Since one type can implement any number of different protocols, it also serves as a form of multiple inheritance.

The key feature in this context is the ability to abstract away the exact storage implementation. During early development, it's very convenient to keep your application state in memory—this makes it easy to iterate on schemas and storage methods, simplifies testing, and generally speeds things along.

When you're running into production, however, using in-memory storage is suddenly a huge hindrance. You don't really have data persistence, access can be inefficient, and you don't have all of the features of a proper database.

Using a storage protocol provides the best of both worlds, albeit at a cost. Once you define what it means to be an instance of application storage, you can have a quick and easy implementation that uses a Clojure atom to use during development, then later on when you have a better understanding about the storage contract and requirements, you can write a second implementation using the database of our choice and seamlessly substitute it in. If you're smart about testing, you can even demonstrate that both implementations are functionally interchangeable.

On the other hand, using a storage protocol rather than specific storage functions adds complexity to the sample application, because there is an extra layer of indirection between the application code and the storage code. This makes it impossible for your editor or IDE to jump directly to the implementation of a storage method, since it won't be resolved until runtime. It also introduces the possibility for divergence between implementations, which is a very pernicious form of technical debt.

These drawbacks apply to protocols in general, and we urge you to think pretty hard about whether a protocol is really necessary for any given task. That said, it's almost always a good idea to use a protocol for storage in a stateful web service like this one, unless you're absolutely sure you'll only ever need one storage implementation.

Creating the Storage Protocol

Our storage protocol is not a handler, a route, or an application, so you need a new namespace. Let's create src/ch3shortener/storage.clj:

(ns ch3shortener.storage)
(defprotocol Storage
  )

Pause for a moment to think about what you need from storage:

  • Create a link with the supplied ID and URL.
  • Retrieve a link, given its ID.
  • Update a link, given its ID and URL.
  • Delete a link, given its ID.
  • List all known links.

Translate them into five storage methods:

(defprotocol Storage
  (create-link [this id url]
    "Store the url under the id. Returns the id if successful, nil if the id is
      already in use.")
  (get-link [this id]
    "Given an ID, returns the associated URL. Returns nil if there is no associated
       URL.")
  (update-link [this id new-url]
    "Updates id to point to new-url. Returns the id if successful, nil if the id
      has not yet been created.")
  (delete-link [this id]
    "Removes a link with the given ID from storage, if it exists.")
  (list-links [this]
    "Returns a map of all known IDs to URLs."))

Testing Protocol Implementations

Even though you can't really test a protocol without an implementation, it's not too early to start writing tests. You know it's important to use tests to show the equivalence of different protocol implementations, and writing these tests ahead of time will help guide the development of your in-memory storage implementation.

The point of using a protocol is to avoid worrying too much about individual implementations, and testing should reflect that. The same exact tests should pass, regardless of the underlying implementation, or else there's probably something wrong. The key here is to write a testing function, rather than a normal test expression. Let's create test/ch3shortener/storage_test.clj:

(ns ch3shortener.storage-test
  (:require [ch3shortener.storage :refer :all]
            [clojure.test :refer :all]))
(defn is-valid-storage ;; note, not deftest
  "Given an implementation of the Storage protocol, assert that it fulfills the
  contract."
  [stg]
  (let [url "http://example.com/clojurebook"
        id "book"]
    (testing "can store and retrieve a link"
      (testing "create-link returns the id"
        (is (= id (create-link stg id url)))
        (testing "and it won't overwrite an existing id"
          (is (nil? (create-link stg id "bogus")))
          (is (= url (get-link stg id))))))
    (testing "can update a link"
      (let [new-url "http://example.com/nevermind"]
        (update-link stg id new-url)
        (is (= new-url (get-link stg id)))))
    (testing "can delete a link"
      (delete-link stg id)
      (is (nil? (get-link stg id)))))
  (testing "can list all links"
    (let [id-urls {"a" "http://example.com/a"
                   "b" "http://example.com/b"
                   "c" "http://example.com/c"}
          ids (doseq [[id url] id-urls]
                (create-link stg id url))
          links (list-links stg)]
      (testing "in a map"
        (is (map? links))
        (testing "equal to the links we created"
          (is (= id-urls links)))))))

It may seem strange that the function's name starts with is, but it's helpful for functions that contain is assertions to be marked this way. You can choose to define a valid-storage predicate that returns true or false, but then you lose granularity that comes from having separate assertions for each of the expected behaviors.

The is-valid-storage function won't do anything until you call it, so the tests will continue to pass for now. But once you do have a concrete storage implementation, you will need to call this function in a deftest form and then know if it passes. For now, keep this around and you can use it for reference while writing the in-memory implementation.

In-Memory Storage

It's time to write a storage implementation. You can put this in the existing storage namespace, but it's a good idea to keep each implementation separate. Let's create src/ch3shortener/storage/in_memory.clj:

(ns ch3shortener.storage.in-memory
  (:require [ch3shortener.storage :refer :all]))

You can implement each storage method inline in one big reify expression, but that would be pretty hard to read. Instead, let's write functions for each storage method one at a time, using the method* naming convention so you don't clash with the storage methods themselves. Each of the functions takes a storage atom (!stg) as a parameter, and most also take a url and/or id.

First, let's handle create-link:

(defn create-link*
  [!stg id url]
  (when-not (contains? @!stg id)
    (swap! !stg assoc id url)
    id))

Remember to check first to see whether the id is already in storage, and if not then nil will be returned. The when-not macro is a big help here, and it also tells later developers that the function could return nil.

Let's move on to get-link, which is almost embarrassingly simple:

(defn get-link*
  [!stg id]
  (get @!stg id))

The storage contract specifies that get-link can return nil, so use get without the optional not-found argument. The update-link implementation is a nice mirror image of create-link:

(defn update-link*
  [!stg id url]
  (when (contains? @!stg id)
    (swap! !stg assoc id url)
    id))

This changes when-not to when, since the methods have opposite requirements. Let's move on to delete-link:

(defn delete-link*
  [!stg id]
  (swap! !stg dissoc id)
  nil)

There's no need to check whether the link actually exists, just simply ensure that it doesn't. Returning nil at the end isn't strictly required, but otherwise swap! will return the entire link map, which is not helpful.

Our last storage method to implement is also the simplest:

(defn list-links*
  [!stg]
  @!stg)

You can do something even shorter, like (def list-links* deref), but this definition is clearer.

Finally, it's time to write a constructor for the storage implementation. The plan here is to construct a blank storage atom and close over it with reify:

(defn in-memory-storage
  []
  (let [!stg (atom {})]
    (reify Storage
      (create-link [_ id url] (create-link* !stg id url))
      (get-link [_ id] (get-link* !stg id))
      (update-link [_ id url] (update-link* !stg id url))
      (delete-link [_ id] (delete-link* !stg id))
      (list-links [_] (list-links* !stg)))))

The reify function takes a protocol and some number of method definitions and returns an object with those defined methods. Since the !stg atom is in scope when reify is called, each of those methods will keep a reference to it for the lifetime of the storage object.

The constructor takes no options, but that's just because it's the simplest possible working storage implementation. A more realistic option for production use involves a database, and the constructor needs to take parameters for at least the database host and credentials. In a typical application, those credentials would come from environment variables and read at runtime using a library like environ.

Now you can test this using the is-valid-storage function defined earlier. Add the following to the end of test/ch3shortener/storage_test.clj:

(deftest in-memory-storage-test
  (let [stg (in-memory/in-memory-storage)]
    (is-valid-storage stg)))

Three leisurely lines is all it takes now to test a new storage implementation! It's unlikely that this form will change; instead, new tests should go in the is-valid-storage function to enforce consistency across implementations.

Separating storage into its own protocol is great for flexibility and it enforces a strict separation of concerns. The rest of the sample application can only use storage methods that are defined (and hopefully tested) ahead of time. Testing storage implementations with a single function provides assurance of consistency while minimizing code duplication.

Handlers

With the storage backend for your service in place, you can write handlers that rely on it. Thanks to the storage protocol, these handlers don't worry about whether the data's stored in an atom, a database, or a message board—they just call the protocol methods on whatever storage object gets passed in.

Let's now take a look at handler.clj and handler_test.clj.

get-link

The get-link handler should take an id and return one of two possible responses:

  • If the link is found in storage, return a 302 redirect to the URL.
  • If the link is not found, return a 404 response.

First, let's require the storage namespace in order to use storage methods:

(ns ch3shortener.handler
  (:require [ring.util.request :as req]
            [ring.util.response :as res]
            [ch3shortener.storage :as st]))

Now implement get-link, using ring.util.response helpers to do most of the work:

(defn get-link
  [stg id]
  (if-let [url (st/get-link stg id)]
    (res/redirect url)
    (res/not-found "Sorry, that link doesn't exist.")))

You may notice that this is more of a "handler" than a handler. That is to say, it doesn't take a full request object, just a link ID. That's okay; you just need to make sure to call it the right way when you add a route for it later on.

Let's add a test to handler_test.clj to make sure that this does what it should. For this test you need a storage object, so you'll also need to require the in-memory-storage function and the storage namespace:

(ns ch3shortener.handler-test
  (:require [clojure.test :refer :all]
            [ring.mock.request :as mock]
            [ch3shortener.handler :refer :all]
            ;; add the following two requirements
            [ch3shortener.storage.in-memory :refer [in-memory-storage]]
            [ch3shortener.storage :as st]))
(deftest get-link-test
  (let [stg (in-memory-storage)
        id "test"
        url "http://test.gov"]
    ;; store a link directly for the test
    (st/create-link stg id url)
    (testing "when the ID exists"
      (let [response (get-link stg id)]
        (testing "the result is a 302"
          (is (= 302 (:status response)))
          (testing "with the expected URL in the Location header"
            (is (= url (get-in response [:headers "Location"])))))))
    (testing "when the ID does not exist"
      (let [response (get-link stg "bogus")]
        (testing "the result is a 404"
          (is (= 404 (:status response))))))))

The tests pass, so you are on the right track. Let's move on to the next handler, create-link.

create-link

This handler takes a response with a POST body and returns one of two responses:

  • If the ID isn't taken, return a 200 response with the short URL.
  • If the ID is taken, return a 422 (unprocessable) response.
(defn create-link
  [stg id {url :body}]
  (if (st/create-link stg id url)
    (res/response (str "/links/" id))
    (-> (format "The id %s is already in use." id)
      res/response
      (res/status 422))))

Testing this can be awkward because it reads the body as if it's a string, but there is no middleware in place yet to make that happen. For the purposes of testing the handler, let's stringify the body by adding this test to handler_test.clj:

(deftest create-link-test
  (let [stg (in-memory-storage)
        url "http://example.com"
        request (-> (mock/request :post "/links/test" url)
                  ;; since we haven't added middleware yet
                  (update :body slurp))]
    (testing "when the ID does not exist"
      (let [response (create-link stg "test" request)]
        (testing "the result is a 200"
          (is (= 200 (:status response)))
          (testing "with the expected body"
            (is (= "/links/test" (:body response))))
          (testing "and the link is actually created"
            (is (= url (st/get-link stg "test")))))))
    (testing "when the ID does exist"
      (let [response (create-link stg "test" request)]
        (testing "the result is a 422"
          (is (= 422 (:status response))))))))

With these tests passing, it's one more handler down, and three to go.

update-link

The update-link handler should have a very similar form to create-link. It should take an ID and a body containing a URL, then:

  • If the ID exists in storage, return a 200 with the shortened URL.
  • If the ID doesn't exist in storage, return a 404 response.
(defn update-link
  [stg id {url :body}]
  (if (st/update-link stg id url)
    (res/response (str "/links/" id))
    (-> (format "There is no link with the id %s." id)
      res/not-found)))

As promised, this is extremely similar to the create-link handler. The tests will also be pretty similar, including the :body workaround:

(deftest update-link-test
  (let [stg (in-memory-storage)
        url "http://example.com"
        request (-> (mock/request :put "/links/test" url)
                  ;; since we haven't added middleware yet
                  (update :body slurp))]
    (testing "when the ID does not exist"
      (let [response (update-link stg "test" request)]
        (testing "the result is a 404"
          (is (= 404 (:status response))))))
    (testing "when the ID does exist"
      (st/create-link stg "test" url)
      (let [new-url "http://example.gov"
            request (assoc request :body new-url)
            response (update-link stg "test" request)]
        (testing "the result is a 200"
          (is (= 200 (:status response)))
          (testing "with the expected body"
            (is (= "/links/test" (:body response))))
          (testing "and the link is actually updated"
            (is (= new-url (st/get-link stg "test")))))))))

Testing the handlers directly can really pay off later. If you run into a problem while testing the full application, the status of the handler tests will tell you a lot about what might be causing the failures.

delete-link

This is another pseudo-handler, since it only needs the storage object and link ID. It also can't really fail, so don't worry about an error response. Since you won't have any useful information to report back to the caller, just return a 204 (no content) response. Here's a nice, quick implementation:

(defn delete-link
  [stg id]
  (st/delete-link stg id)
  (-> (res/response "")
    (res/status 204)))

Note, the empty string is returned, rather than omitting the body entirely. This is standard practice for responses that don't have a body. The tests are just a little bit more involved:

(deftest delete-link-test
  (let [stg (in-memory-storage)
        id "test"
        url "http://example.com/foo"]
    (testing "when the link exists"
      (st/create-link stg id url)
      (let [response (delete-link stg id)]
        (testing "the response is a 204"
          (is (= 204 (:status response))))
        (testing "the link is deleted"
          (is (nil? (st/get-link stg id))))))
    (testing "when the link does not exist"
      (let [response (delete-link stg "bogus")]
        (testing "the response is still 204"
          (is (= 204 (:status response))))))))

You're almost done writing the handlers, but there is still one more to go.

list-links

This handler is going to return a JSON response, so it's time to add a couple of new dependencies to project.clj:

  :dependencies [[org.clojure/clojure "1.7.0"]
                 [compojure "1.4.0"]
                 [ring/ring-defaults "0.1.5"]
                 ;; add these two:
                 [ring/ring-json "0.4.0"]
                 [cheshire "5.5.0"]]

Both of these libraries were mentioned before, but to review: The ring/ring-json library contains middleware that you can use to help return a JSON response, while cheshire is a more general-purpose JSON library that you'll be using in tests.

Let's require wrap-json-response in the handler namespace first:

(ns ch3shortener.handler
  (:require [ring.util.request :as req]
            [ring.util.response :as res]
            [ring.middleware.json :refer [wrap-json-response]] ;; new line
            [ch3shortener.storage :as st]))

Since there is a plan to have one JSON route, you might as well wrap the handler directly, so let's write the final handler:

(defn list-links
  "Returns a handler! Call the handler if you want a response."
  [stg]
  (wrap-json-response
   (fn [_] ;; we don't need the request at this point
     (res/response (st/list-links stg)))))

This function is definitely a little odd, since it closes over the storage object and returns a handler, so calling the handler is going to take some extra parentheses. The advantage to doing it this way is that you can wrap the inner handler with wrap-json-response directly, avoiding any need to manually encode the body or set the content type. All you have to do is put the map into the response body and you're done.

Let's have a look at the tests. First, add one more dependency to the handler-test namespace:

(ns ch3shortener.handler-test
  (:require [clojure.test :refer :all]
            [ring.mock.request :as mock]
            [ch3shortener.handler :refer :all]
            [ch3shortener.storage.in-memory :refer [in-memory-storage]]
            [ch3shortener.storage :as st]
            [cheshire.core :as json])) ;; add this

Use cheshire to decode the response from the new handler. After that, add the list-links-test to the end of the namespace:

(deftest list-links-test
  (let [stg (in-memory-storage)
        id-urls {"a" "http://link.to/a"
                 "b" "http://link.to/b"
                 "c" "http://link.to/c"}]
    (doseq [[id url] id-urls]
      (st/create-link stg id url))
    (let [handler (list-links stg)
          response (handler (mock/request :get "/links"))
          parsed-links (json/decode (:body response))]
      (testing "the response is a 200"
        (is (= 200 (:status response))))
      (testing "with a body that decodes to the original map"
        (is (= id-urls parsed-links))))))

Note how we're calling (list-links stg) to get the handler, not a response. Proceed to call the handler with the kind of request expected (even though it doesn't use the request), and then parse the response as JSON.

The first, last, and most important thing to remember about handlers is that they are ordinary Clojure functions that take and return maps. The Clojure standard library comes with dozens of functions that work on maps, and those functions can help you write handlers. Additionally, Ring itself comes with a wealth of useful handler-specific functions, found in ring.util.request and ring.util.response.

Middleware

We have middleware to handle the JSON responses, but you need to bring back wrap-slurp-body. Let's add it to src/ch3shortener/middleware.clj:

(ns ch3shortener.middleware
  (:import [java.io InputStream]))
(defn wrap-slurp-body
  [handler]
  (fn [request]
    (if (instance? InputStream (:body request))
      (let [prepared-request (update request :body slurp)]
        (handler prepared-request))
      (handler request))))

There were no specific tests for this middleware before, but you can add some by switching over to test/ch3shortener/middleware_test.clj:

(ns ch3shortener.middleware-test
  (:require [ch3shortener.middleware :refer :all]
            [ring.mock.request :as mock]
            [clojure.test :refer :all]))
(deftest wrap-slurp-body-test
  (let [body "This is a body."
        request (mock/request :post "/foo" body)
        expected-request (assoc request :body body)
        identity-handler (wrap-slurp-body identity)]
    (testing "when given a request with a ByteArrayInputStream body"
      (let [prepared-request (identity-handler request)]
        (testing "the body is turned into a string"
          (is (= body (:body prepared-request)))
          (testing "and the rest of the request is unchanged"
            (is (= expected-request prepared-request))))))
    (testing "when given a request that has no body"
      (let [no-body (mock/request :get "/")]
        (testing "there's no effect"
          (is (= no-body (identity-handler no-body))))))
    (testing "applying the middleware a second time has no effect"
      (let [request (mock/request :post "/foo" body)]
        (is (= expected-request
               (-> request
                 identity-handler
                 identity-handler)))))))

Rather than testing the middleware with a real handler, use the middleware to wrap Clojure's identity function. All identity does is return its argument, so when the wrapped "handler" is passed a request, you get the prepared request back. It's a neat trick that works well as long as your middleware doesn't ever expect a Ring response.

The tests should demonstrate that the middleware ignores requests with missing bodies and requests where the body isn't an InputStream, but otherwise converts the body into the right string. With that out of the way, let's move onto routes.

Routes

The handlers are tested and ready, but we haven't served anything over HTTP! That's because you still need to set up some routes. This is the last step before you can run the server locally, and it is time to do it.

These routes have something that was missing from the earlier examples—a data dependency. Much like the in-memory-storage constructor, you're going to write a function that closes over a dependency and returns the thing you actually want, which is a Compojure handler.

Here's an empty version, just to have something to start with. Go ahead and add this to the bottom of src/ch3shortener/routes.clj:

(defn shortener-routes
  [stg]
  (routes
   (route/not-found "Not Found")))

It's important to have this function defined early, though, because the tests going forward will happen at the application-level from now on.

Let's move over to src/ch3shortener/application.clj, where two changes must be made. First, construct a storage object to pass into the handler; then use that to get the Compojure routes:

(ns ch3shortener.application
  (:require [ring.middleware.defaults :refer [wrap-defaults api-defaults]]
            [ch3shortener.storage.in-memory :refer [in-memory-storage]]
            [ch3shortener.routes :as routes]))
(def app
  (let [stg (in-memory-storage)
        app-routes (routes/shortener-routes stg)]
    (wrap-defaults app-routes api-defaults)))

This temporarily breaks almost every application test we have, so let's clear out test/ch3shortener/application_test.clj a little bit:

(deftest test-app
  (testing "not-found route"
    (let [response (app (mock/request :get "/invalid"))]
      (is (= (:status response) 404)))))

Once that's done, we'll have a storage-backed 404 service that you can run with lein ring server. As we add routes over the course of the rest of the section, they'll also become active as soon as you re-run lein ring server, so feel free to try them out.

POST /links/:id

Let's start with the C in CRUD, POST /links/:id. The handler written for this endpoint takes an ID and the request body, but the body has to be turned into a string first. Let's add the route and the middleware to shortener-routes:

(defn shortener-routes
  [stg]
  (-> (routes
       (POST "/links/:id" [id :as request] (handler/create-link stg id request))
       (route/not-found "Not Found"))
    (wrap-routes mw/wrap-slurp-body)))

The create-link handler takes both the id and the full request map, so grab the former by matching on its name in the binding vector and the latter using :as destructuring.

Notice that you're indiscriminately wrapping everything with wrap-slurp-body for now, and that you're using wrap-routes to do it. With these tests it's safe to use wrap-slurp-body, but you need to use wrap-routes to make sure the middleware only gets called when a route matches. Otherwise, you're risking destroying the contents of a request body.

In terms of tests, make sure that the route is matching and behaving somewhat expectedly. Let's add a simple example to the ch3shortener.application-test namespace:

(deftest test-app
  (let [url "http://example.com/post"
        id "test"
        path (str "/links/" id)]
    (testing "creating a link"
      (let [response (app (mock/request :post path url))]
        (is (= 200 (:status response)))
        (is (= path (:body response))))))
  (testing "not-found route"
    (let [response (app (mock/request :get "/invalid"))]
      (is (= (:status response) 404))))) 

You don't have a way to test whether creating the link is effective yet, but you will soon. Until then, the fact that the handler and the storage layer are both tested should give you some confidence that things are going well.

GET /links/:id

Now that you can create short links, it's time to actually use them. Let's add a route that will redirect you to the URL you want when you give it a short link ID. Add this to shortener-routes:

(defn shortener-routes
  [stg]
  (-> (routes
       (POST "/links/:id" [id :as request] (handler/create-link stg id request))
       (GET "/links/:id" [id] (handler/get-link stg id))
       (route/not-found "Not Found"))
    (wrap-routes mw/wrap-slurp-body)))

Augment the tests to verify that both link creation and retrieval work:

(deftest test-app
  (let [url "http://example.com/post"
        id "test"
        path (str "/links/" id)]
    (testing "creating a link"
      (let [response (app (mock/request :post path url))]
        (is (= 200 (:status response)))
        (is (= path (:body response)))))
    (testing "visiting a link"
      (testing "when the link exists"
        (let [response (app (mock/request :get path))]
          (testing "returns a 302"
            (is (= 302 (:status response)))
            (testing "with the correct location"
              (is (= url (get-in response [:headers "Location"])))))))
      (testing "when the link does not exist"
        (let [response (app (mock/request :get "/links/nothing"))]
          (testing "returns a 404"
            (is (= 404 (:status response))))))))
  (testing "not-found route"
    (let [response (app (mock/request :get "/invalid"))]
      (is (= (:status response) 404)))))

This is called an MVP (Minimum Viable Product). You can now shorten a link and redirect to it when asked to do so. You still need to implement updating, deleting, and listing.

PUT /links/:id

It's time to implement the mirror-universe version of link creation: link updating. This should be easy since it's so similar to POST /links/:id. Just add this line to shortener-routes:

(defn shortener-routes
  [stg]
  (-> (routes
       (POST "/links/:id" [id :as request] (handler/create-link stg id request))
       (PUT "/links/:id" [id :as request] (handler/update-link stg id request))
         ;; new
       (GET "/links/:id" [id] (handler/get-link stg id))
       (route/not-found "Not Found"))
    (wrap-routes mw/wrap-slurp-body)))

Add this new deftest form to the end of ch3shortener.application-test:

(deftest link-updating
  (let [id "put"
        url "http://example.com/putTest"
        path (str "/links/" id)]
    (testing "when the link does not exist"
      (let [response (app (mock/request :put path url))]
        (testing "the response is a 404"
          (is (= 404 (:status response))))))
    (testing "when the link does exist"
      (app (mock/request :post path "http://example.post"))
      (let [response (app (mock/request :put path url))]
        (testing "the response is a 200"
          (is (= 200 (:status response))))))))

You are now up to CRU on the CRUD scale, so just a little further to go.

DELETE /links/:id

This should be getting routine by now. Add a single line to shortener-routes:

       (DELETE "/links/:id" [id] (handler/delete-link stg id))

And a quick test to make sure it works:

(deftest delete-link
  (let [id "thing"
        url "http://example.com/thing"
        path (str "/links/" id)]
    (testing "when the link doesn't exist"
      (let [response (app (mock/request :delete path))]
        (testing "the result is a 204"
          (is (= 204 (:status response))))))
    (testing "when the link does exist"
      (app (mock/request :post path url))
      (let [response (app (mock/request :delete path))]
        (testing "the response is still a 204"
          (is (= 204 (:status response)))
          (testing "and the link is now a 404"
            (is (= 404 (:status (app (mock/request :get path)))))))))))

GET /links

All right, it's time for the final endpoint in the example service. You may remember that this handler is a little different, because you need to call the function to construct it. Well, thankfully Compojure handles that perfectly well without you having to do anything particularly unusual. Go ahead and add this line somewhere inside shortener-routes:

       (GET "/links" [] (handler/list-links stg))

When Compojure matches this route, it evaluates the form on the right and checks the type of the return value. If it's a function, it assumes that it's a handler and passes it the request map. In this case, the form does evaluate to a handler, so everything's fine.

In order to test this, you'll need cheshire again, so add this line to the application-test namespace declaration:

(ns ch3shortener.application-test
  (:require [ch3shortener.application :refer :all]
            [clojure.test :refer :all]
            [ring.mock.request :as mock]
            [cheshire.core :as json] ;; add this
            [clojure.string :as str]))

Then add this somewhere else in the file:

(deftest list-links
  ;; first make sure there are some links to list
  (let [id-urls {"a" "http://example.com/a"
                 "b" "http://example.com/b"
                 "c" "http://example.com/c"}]
    (doseq [[id url] id-urls]
      (app (mock/request :post (str "/links/" id) url)))
    (let [response (app (mock/request :get "/links"))
          parsed-body (json/decode (:body response))]
      (testing "the response is a 200"
        (is (= 200 (:status response)))
        (testing "and the decoded body contains all of the links we added"
          (is (= id-urls
                 (select-keys parsed-body ["a" "b" "c"]))))))))

That concludes the implementation of the link-shortener, from start to finish. It may be simple, but it has a solid foundation and a well-defined structure that make it a good codebase to iterate on and expand. The minimal storage implementation is ready to be swapped out for something more robust if and when that becomes necessary. The majority of the functions are short and focused, taking ordinary Clojure data as arguments and returning ordinary Clojure data structures, which helps to make it accessible to developers without much specialized experience.

Finally, tests at multiple levels of composition (handlers, middleware, routes) give you confidence that the application works as designed—and they'll help narrow down the root cause if something does show unexpected behavior. If you add other storage implementations, you can test that they truly are interchangeable with just a few lines of code.

If you're feeling up to the challenge, now would be a great time to experiment with adding authentication, writing a new storage implementation, or adding a front end for the service in Clojurescript! There's no substitute for creative tinkering, and now that you understand the fundamentals, you'll be able to take this project in any direction that interests you.

DEPLOYMENT

So far, you've mainly interacted with the shortener service by calling the handler in tests with ring-mock in this chapter. You may also have used lein ring server and experimented a bit, but you don't want to be one of the people who use that command for a production web service.

In this section, let's examine a few other options for running a Clojure web service, either in development, a production server of your own, or a cloud hosting service like Heroku.

Using Leiningen

You've already seen lein ring server (or lein ring server-headless), but it's worth pointing out that this option is actually pretty flexible. First, all of the lein ring functionality is provided by the creatively-named lein-ring plugin. The options we're about to cover here are actually handled by that plugin rather than by Ring directly, so keep that in mind.

You saw earlier that you can set the default Ring handler in project.clj with this line:

:ring {:handler example-project.handler/app}

But that's just the start. A more complete set of lein-ring options looks more like this:

:ring {:init example-project.app/init ;; called when the handler starts
       :destroy example-project.app/destroy ;; called when the handler exits
       :adapter example-projects.app/adapter-opts ;; passed to the ring adapter
       :handler example-project.handler/app}

The :init and :destroy keys should both point to functions that can be called with no arguments to initialize and dispose of any resources your handlers might need at runtime. In general, you probably won't see these used very frequently. It's usually a better idea to handle your application setup and teardown yourself, preferably using something like Stuart Sierra's component or Puppet Labs' trapperkeeper, both of which are designed to manage the lifecycles of long-running services.

The :adapter key, if present, should point to a map of options that will be passed to the Ring adapter. The term "adapter" in Ring parlance is essentially the HTTP server itself. Ring uses Jetty by default, and it's quite good for most purposes, but it is swappable. Whether you choose to substitute the adapter or not, you may want to further configure the adapter to listen on a different port, tune the number of threads available for requests, or encrypt traffic using specific SSL certificates. Note that this will have no effect if you compile your project into a WAR file.

The lein ring server process listens on the first open port it finds, starting at 3000 and counting up. If you would prefer for it to listen to a different port, you can either add the port as a command-line argument (e.g., lein ring server 8080), or change it in the configuration map like this:

:ring {:handler example-project.handler/app
       :port 8080}

It's not a good idea to try to use this to run on port 80, or any other privileged port. Doing so would require you to run lein as root, which is actively discouraged by Leiningen. If you do want to run on a port below 1024, you're much better off building an uberjar and running that.

Using the lein-ring plugin is very convenient during development, but it has some clear disadvantages. Running anything in Leiningen has a performance cost, since it uses JVM options that prioritize startup time over general speed. The restriction against running on a privileged port, though not insurmountable, makes things at least unpleasant. In general, lein ring server is not suitable for use in production.

Compiling an Uberjar or Uberwar

The preferred way to deploy a dedicated Clojure web service is as an uberjar. An uberjar is a JAR (Java Archive) file that contains not only your project, but every single dependency, including all of the Clojure compiler and standard library.

In most Clojure projects, you create an uberjar using the lein uberjar command. Ring projects are slightly different, since they need to include a main function to start a server and register the application handler, but creating them is almost as easy when you're using lein-ring. Instead of lein uberjar, just run lein ring uberjar. Once you have the uberjar, you can run it with java -jar project-name.jar. Take the shortener service developed earlier as an example:

∼/src/ch3shortener$ lein ring uberjar
Compiling ch3shortener.application.main
Created target/ch3shortener-0.1.0-SNAPSHOT.jar
Created target/ch3shortener-0.1.0-SNAPSHOT-standalone.jar
∼/src/ch3shortener$ java -jar target/ch3shortener-0.1.0-SNAPSHOT-standalone.jar
2016-02-03 01:51:11.225:INFO:oejs.Server:jetty-7.x.y-SNAPSHOT
2016-02-03 01:51:11.287:INFO:oejs.AbstractConnector:Started
  [email protected]:3000
Started server on port 3000 

The port here is determined either by the :port setting in the :ring map or the :adapter settings, if any. In this case it's using the default of 3000 (or the next available).

Uberjars are great because they're portable to any system with Java installed; you don't necessarily need to worry about whether or how another web server is installed and configured, nor any other dependencies. Even though it's common to use another web server like Nginx or Apache to proxy to your Clojure service, it's not always necessary.

In some organizations, there may already be infrastructure in place to run Java WAR (Web Archive) files. In that case, there's a WAR counterpart to the uberjar called an uberwar. Creating it is, as you'd expect, as simple as lein ring uberwar. Running the uberwar is considerably more complicated, and is almost entirely dependent on the application server running the WAR file.

Hosting

If you have access to a shell on your target host, your deployment options are limitless. In the simplest case, you can get away with simply configuring your service to run on the appropriate port and running the uberjar directly.

But you can't always rely on that level of access to the host, and you may not want to. If you want to run your application on AWS (Amazon Web Services) Elastic Beanstalk or Heroku, you'll have to jump through another hoop or two initially, but ultimately you'll spend far less time managing the configuration of your server.

Heroku

Heroku is a PaaS (Platform as a Service) solution for serving web applications written in almost any language, including Clojure. It's a favorite of hobbyists because it offers a free tier that's suitable for personal or demonstration use, but also easily scales up for more serious projects.

Heroku has excellent support for deploying Ring-based Clojure applications like the link shortener from earlier in this chapter. The exact steps for deploying are subject to change at any time, so it's best to follow Heroku's official documentation for Clojure services.

Amazon Elastic Beanstalk

Amazon Elastic Beanstalk is a PaaS offered by Amazon as part of the AWS (Amazon Web Services) collection of products. It doesn't have the same kind of official support for Clojure that Heroku does, but that hasn't stopped the Clojure community from adopting it as a platform.

There are two main approaches to deploying to Elastic Beanstalk. The first takes advantage of Java interoperability to use Amazon's own Java API. The most convenient way to do this is with the lein-beanstalk plugin, which adds new Leiningen tasks for deploying to AWS. Once you add the plugin to your project and provide your AWS credentials, it's as simple as lein beanstalk deploy {environment}.

The second method relies on Docker, a lightweight virtualization technology that relies on the Linux kernel for process isolation rather than running a full virtual machine. Elastic Beanstalk supports running software from Docker containers, so once you containerize your Clojure service you can run it on AWS or several other cloud providers.

SUMMARY

Writing web services in Clojure the right way is a real joy, in no small part because of the simple, powerfully composable abstractions that Clojure service developers work with. Once you understand request and response maps, you can understand handlers. And once you understand handlers, you can understand middleware. Combine those with routes and you're unstoppable!

The libraries we've covered here are popular, but they're far from the only ones you'll see in the Clojure ecosystem. Although frameworks are not especially popular, there are certainly some commonly-used libraries that veer into that territory. Take Liberator, for instance. Liberator is a Clojure library that takes a much more active role in the definition of your API by encouraging you to adhere to the HTTP specifications in a very defined way. Liberator resources (a form of specialized handler) resemble a map of functions marked by keywords like :handle-ok, :malformed?, and :delete. Liberator will pass a request through these functions is a painstakingly documented order to determine what type of response to return.

For example, instead of choosing to return a 400 error, as was done in the shortener earlier, it will return a 400 response if and only if it has defined a :malformed? predicate that returns true. If the predicate returns false, Liberator will then call the :authorized? function (if defined), followed by :allowed?. The flow is complicated enough that developers on projects that use Liberator have invariably bookmarked the Liberator decision graph, of which this is just a small portion (see Figure 3.2):

Schematic of Liberator decision graph.

Figure 3.2

Although it removes a lot of flexibility from a project, Liberator has its uses and it can make a complicated API much easier to maintain. It's based on Ring, so it's fully compatible with any Ring middleware, and it works well with Compojure or other Ring-based routing libraries.

Luminus, a "micro-framework" for Clojure, has also garnered some attention. It's based mainly on the same libraries covered in this chapter—Ring and Compojure—while packaging a few others, and imposing a specific project structure and layout. It also provides great out-of-the-box support for deployment. Luminus seeks to walk the line between convenience and flexibility, and many developers really like it.

Once you understand the fundamental abstractions of Clojure web services, you'll find that it's much easier to choose the libraries that help you the most without getting in your way. There's no one approach that works for every project, and being able to mix and match libraries to suit your needs is one of the great advantages that Clojure offers that other languages can't match.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset