# Clojure/REST (pt. 2)

## Malcolm Sparks

## @malcolmsparks

##

## Let's review

### The good points

- Simple & easy
- Idiomatic
- Good as an example
- Exercises many parts of REST
- Stateful, resource-based

### No content negotiation

- Only produces HTML (in UTF-8)
- Only speaks English
- No support for compression (e.g. gzip)
- No machine-readable API

### Inefficient

- No cache-control headers
- Doesn't support If-Modified-Since (dates)
- Doesn't support If-Not-Match (etags)
  - Responsive UIs need fast server responses
  - om.next
  - What about your upstream systems?

                           /------- Other system 1
    om.next               /
    Browser <--------- Server -------- Other system 2
                          \
                           \------ Other system 3

### Vulnerable to attack

- No security headers
- Ad-hoc parameter validation

### No service metadata

- No data about what the service does (e.g. Swagger, RAML)
- No HEAD, OPTIONS or TRACE methods

### Blocking I/O

- Unlike Play, Ratpack, Vert.x, Dropwizard, etc.

### Ad-hoc

- 'Hand-crafted'
- Hypermedia links are hand-coded
- Services can't be (easily) stamped-out
- Consequences?
  - Lots of similar yet inconsistent (and buggy) code
  - Business logic coupled to infrastructure
  - 'YAGNI' mentality to REST

## What are our options?

- Functional model
  - Function composition (Ring middleware)
- Execution model
  - Liberator
- Resource model
  - Define resource using a Clojure map

### yada

yada

### API

    (def handler (yada data))

### features

- Parameter validation (and coercion)
- Automatic full HTTP compliance
  - correct HTTP method semantics, response codes
  - content negotiation, allow, vary
  - conditional requests, entity tags
  - custom methods/media-types
  - security headers, CORS
  - range-requests, transfer-encoding, multipart
- Automatic Swagger spec. generation
- Async 'on demand'
  - database trips, SSE, streaming downloads/uploads

## The phonebook index

    {:description "Phonebook index"
     :properties {…}
     :methods {:get {…}
               :post {…}}

### GET

    {:get
     {:parameters {:query {(s/optional-key :q) String}}

      :produces [{:media-type #{"text/html" "application/json;q=0.9"}
                  :charset "UTF-8"}]

      :handler
      (fn [ctx]
        (let [q (get-in ctx [:parameters :query :q])
              entries (if q
                        (db/search-entries db q)
                        (db/get-entries db))]
          (case (get-in ctx [:response :representation :media-type :name])
            "text/html" (html/index-html entries @*routes q)
            entries)))}}

### POST

    {:post
     {:parameters
      {:form {:surname String :firstname String :phone [String]}}

      :consumes
      [{:media-type "application/x-www-form-urlencoded"
        :charset "UTF-8"}]

      :handler
      (fn [ctx]
        (let [id (db/add-entry db (get-in ctx [:parameters :form]))]
          (-> (:response ctx)
              (assoc :status 303)
              (update :headers merge
                {"location"
                 (bidi/path-for @*routes ::entry :entry id)}))))}}

## The phonebook entry

    {:description "Phonebook entry"
     :parameters {:path {:entry Long}}
     :properties (fn [ctx] {:last-modified …
                            :version …})
     :produces {:media-type #{"text/html"
                              "application/json;q=0.8"}
                :charset "UTF-8"}
     :methods {:get {…}
               :put {…}
               :delete {…}}}

### Phonebook entry GET

    {:get
     {:handler
      (fn [ctx]
        (when-let [entry (db/get-entry db
                           (get-in ctx [:parameters :path :entry]))]
          (case (get-in ctx [:response :representation :media-type :name])
            "text/html" (html/entry-html entry)
            entry)))}}

### Phonebook entry DELETE

    {:delete
     {:handler
      (fn [ctx]
        (let [id (get-in ctx [:parameters :path :entry])]
          (db/delete-entry db id)))}}

### Phonebook entry PUT

    [:button {:onclick (format "phonebook.update('%s')" entry)} "Update"]

    update: function(url) {
        x = new XMLHttpRequest()
        x.open("PUT", url)
        // FormData is built-in, sends multipart/form-data
        x.send(new FormData(document.getElementById("entry")))
    }

### Phonebook entry PUT (pt. 2)

    {:put
     {:consumes [{:media-type #{"multipart/form-data"}}]

      :parameters
      {:form {:surname String
              :firstname String
              :phone [String]
              :photo java.io.File}}

      :handler
      (fn [ctx]
        (let [entry (get-in ctx [:parameters :path :entry])
              form (get-in ctx [:parameters :form])]
          (db/update-entry db entry form)))}}

### References

- https://github.com/juxt/yada