Compojure

Like most of James Reeves’ libraries, Compojure[22] inspires us to strive for elegance in our own work. If you’ve done any web work with Clojure, at some point you’ve probably used Compojure, on top of Ring,[23] to handle your HTTP routing. A typical app using Compojure starts out something like this:

apis/compojure_1.clj
 
(​ns​ hello-world
 
(:require [compojure.core :refer [defroutes GET]]
 
[compojure.route :refer [not-found]]))
 
 
(defroutes app
 
(GET ​"/"​ [] ​"Hello World"​)
 
(not-found ​"Page not found"​))

Before we look at the implementations of the Compojure bits that we’re using, take a moment to consider how concise this code is. Beyond the opening ns form, we need only a few lines of code (plus a bit of wiring in the project.clj file) to launch a local web app with lein-ring.[24] We have a defroutes expression that defines a var that we can hand to the server infrastructure, a GET expression that defines a function to be executed upon a GET request to the root URL, and a not-found expression that defines the responder function for any request that didn’t match the other routes.

Now let’s see how Compojure makes things so nice:

apis/compojure_2.clj
 
(​defmacro​ defroutes
 
"Define a Ring handler function from a sequence of routes. The name may
 
optionally be followed by a doc-string and metadata map."
 
[​name​ & routes]
 
(​let​ [[​name​ routes] (name-with-attributes ​name​ routes)]
 
`(​def​ ~​name​ (routes ~@routes))))

That’s a pretty small macro, right? There’s not much code at all, and a lot of it is using name-with-attributes from tools.macro,[25] a handy tool that lets your custom def-like macros act more like what people are used to, allowing docstrings and metadata maps. And the only call that remains, routes (remember that the invocation of routes on the last line is resolved as compojure.core/routes due to the syntax-quote), is just a function. So defroutes is really just a nice layer of syntax to allow you to type this:

apis/compojure_3.clj
 
(defroutes app
 
(GET ​"/"​ [] ​"Hello World"​)
 
(not-found ​"Page not found"​))

instead of the following:

apis/compojure_4.clj
 
(​def​ app
 
(routes
 
(GET ​"/"​ [] ​"Hello World"​)
 
(not-found ​"Page not found"​)))

There’s only a small difference between the two, that you’d give def a docstring in a different way than you would with defroutes. But the main benefit of defroutes here is that it eliminates an extra expression.

For many folks, this doesn’t seem like a big win, and the great news for them is that because James has built this library the way he did, they don’t have to use defroutes. So if you’re among that crowd, you can use the def version instead, and of course you get the same code that defroutes macroexpands to. But for those who prefer the defroutes route, the more concise option is also available.

Of course, choosing to use the underlying function version of an API isn’t always so easy. As it turns out, not-found is already just a function, but let’s see what GET’s implementation looks like:

apis/compojure_5.clj
 
(​defn-​ compile-route
 
"Compile a route in the form (method path & body) into a function."
 
[method route bindings body]
 
`(make-route
 
~method ~(prepare-route route)
 
(​fn​ [request#]
 
(let-request [~bindings request#] ~@body))))
 
 
(​defmacro​ GET ​"Generate a GET route."
 
[path args & body]
 
(compile-route :get path args body))

Here the case for a macro is more compelling, because without GET you’d need something like:

apis/compojure_6.clj
 
(​ns​ hello-world.handler
 
(:require [clout.core :refer [route-compile]]
 
[compojure.core :refer [routes make-route]]
 
[compojure.route :refer [not-found]]))
 
 
(​def​ app
 
(routes
 
(make-route :get (route-compile ​"/"​)
 
(​fn​ [request] ​"Hello World"​))
 
(not-found ​"Page not found"​)))

Even this is selling GET (and its helper functions and macros) a bit short; prepare-route and let-request let you extract pieces of the request and bind them to symbols in the body of the request-servicing function. GET gives us a destructuring feature that we’re not using at the moment. Nevertheless, two things are clear. First, the version of this web app using defroutes and GET is much more pleasant to look at and it’s easier to quickly understand what’s happening. And second, if we’re so motivated, we can strip away the macro layer and use the underlying functions.

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

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