Slide 1

Slide 1 text

Sane, smart, fast, Clojure web services — Rook Howard Lewis Ship - [email protected] - @hlship

Slide 2

Slide 2 text

Why?

Slide 3

Slide 3 text

No content

Slide 4

Slide 4 text

Liberator (defresource entry-resource [id] :allowed-methods [:get :put :delete] :known-content-type? #(check-content-type % ["application/json"]) :exists? (fn [_] (let [e (get @entries id)] (if-not (nil? e) {::entry e}))) :existed? (fn [_] (nil? (get @entries id ::sentinel))) :available-media-types ["application/json"] :handle-ok ::entry :delete! (fn [_] (dosync (alter entries assoc id nil))) :malformed? #(parse-json % ::data) :can-put-to-missing? false :put! #(dosync (alter entries assoc id (::data %))) :new? (fn [_] (nil? (get @entries id ::sentinel))))

Slide 5

Slide 5 text

Resource vs. Endpoints

Slide 6

Slide 6 text

Rook — Goals & Philosophy

Slide 7

Slide 7 text

Goal: Simple Functions

Slide 8

Slide 8 text

(ns org.example.resources.health-check)
 
 (defn ping
 {:route [:get []]}
 []
 {:status 200
 :headers {}
 :body "Health check A-OK!"})

Slide 9

Slide 9 text

(ns org.example.server (:require [ring.adapter.jetty :as jetty] [io.aviso.rook :as rook])) (defn start-server [port] (let [handler (-> (rook/namespace-handler ['org.example.resources.health-check]) rook/wrap-with-standard-middleware)] (jetty/run-jetty handler {:port port :join? false})))

Slide 10

Slide 10 text

$ http localhost:8080 HTTP/1.1 200 OK Content-Length: 18 Date: Thu, 26 Mar 2015 23:49:54 GMT Server: Jetty(7.6.13.v20130916) Health check A-OK!

Slide 11

Slide 11 text

Goal:
 Simple/Smart Access to Request Data

Slide 12

Slide 12 text

(ns org.example.resources.health-check)
 
 (defn ping
 {:route [:get [:id]]}
 [request id]
 {:status 200
 :headers {}
 :body {:uri (:uri request)
 :message (format "Health check %s A-OK!" id)}}) 


Slide 13

Slide 13 text

$ http localhost:8080 HTTP/1.1 404 Not Found Cache-Control: must-revalidate,no-cache,no-store Content-Length: 1267 Content-Type: text/html;charset=ISO-8859-1 Date: Thu, 26 Mar 2015 23:59:19 GMT Server: Jetty(7.6.13.v20130916) Error 404 Not Found

HTTP ERROR: 404

Problem accessing /. Reason:

 Not Found


Powered by Jetty://

Slide 14

Slide 14 text

$ http localhost:8080/gnip HTTP/1.1 200 OK Content-Length: 51 Content-Type: application/json; charset=utf-8 Date: Fri, 27 Mar 2015 00:00:36 GMT Server: Jetty(7.6.13.v20130916) { "message": "Health check gnip A-OK!", "uri": "/gnip" }

Slide 15

Slide 15 text

(ns org.example.resources.health-check)
 
 (defn ping
 {:route [:get [:id]]}
 [request id]
 {:status 200
 :headers {}
 :body {:uri (:uri request)
 :message (format "Health check %s A-OK!" id)}}) 


Slide 16

Slide 16 text

Goal:
 Free From Globals

Slide 17

Slide 17 text

(ns org.example.resources.offers
 (:require [io.aviso.rook.utils :as utils]))
 
 (defn show
 [db-conn id]
 (utils/response {:db-conn db-conn
 :id id}))

Slide 18

Slide 18 text

(ns org.example.complex-server
 (:require
 [ring.adapter.jetty :as jetty]
 [io.aviso.rook :as rook]
 [io.aviso.rook.server :as server]))
 
 (defn create-handler [database-connection]
 (-> (rook/namespace-handler {:arg-resolvers {'db-conn (fn [request] database-connection)}}
 ["offers" 'org.example.resources.offers]
 ["users" 'org.example.resources.users])
 rook/wrap-with-standard-middleware))
 
 (defn start-server
 [database-connection port]
 (jetty/run-jetty (server/construct-handler {:log true :reload true} #'create-handler database-connection)
 {:port port :join? false}))

Slide 19

Slide 19 text

$ http localhost:8080/offers/1234 HTTP/1.1 200 OK Content-Length: 44 Content-Type: application/json; charset=utf-8 Date: Fri, 27 Mar 2015 00:21:30 GMT Server: Jetty(7.6.13.v20130916) { "db-conn": "mock-db-connection", "id": "1234" }

Slide 20

Slide 20 text

(ns org.example.complex-server
 (:require
 [ring.adapter.jetty :as jetty]
 [io.aviso.rook :as rook]
 [io.aviso.rook.server :as server]))
 
 (defn create-handler [database-connection]
 (-> (rook/namespace-handler {:arg-resolvers {'db-conn (fn [request] database-connection)}}
 ["offers" 'org.example.resources.offers]
 ["users" 'org.example.resources.users])
 rook/wrap-with-standard-middleware))
 
 (defn start-server
 [database-connection port]
 (jetty/run-jetty (server/construct-handler {:log true :reload true} #'create-handler database-connection)
 {:port port :join? false}))

Slide 21

Slide 21 text

{:arg-resolvers {'db-conn (fn [request] database-connection)} Symbol to match Resolves the argument value from the request

Slide 22

Slide 22 text

{:arg-resolvers {'db-conn (constantly database-connection)} Symbol to match Resolves the argument value from the request

Slide 23

Slide 23 text

(ns org.example.complex-server
 (:require
 [ring.adapter.jetty :as jetty]
 [io.aviso.rook :as rook]
 [io.aviso.rook.server :as server]))
 
 (defn create-handler [database-connection]
 (-> (rook/namespace-handler {:arg-resolvers {'db-conn (fn [request] database-connection)}}
 ["offers" 'org.example.resources.offers]
 ["users" 'org.example.resources.users])
 rook/wrap-with-standard-middleware))
 
 (defn start-server
 [database-connection port]
 (jetty/run-jetty (server/construct-handler {:log true :reload true} #'create-handler database-connection)
 {:port port :join? false})) Development mode: the handler is recreated on each request

Slide 24

Slide 24 text

Goal: Leverage Metadata

Slide 25

Slide 25 text

(defn ping
 {:route [:get [:id]]}
 [request id]
 {:status 200
 :headers {}
 :body (format "Health check %s A-OK!" id)})


Slide 26

Slide 26 text

(defn create
 {:schema create-schema
 :responses create-responses
 :required-permissions "auth/user/create"
 :operation-trace "Creating new user."}
 […] …)

Slide 27

Slide 27 text

Endpoint Middleware (fn [handler endpoint-metadata]) ➠ handler Meta-data of function merged w/ namespace metadata (to provide defaults)

Slide 28

Slide 28 text

(rook/compose-middleware
 tracking/wrap-with-operation-tracking
 (rv/wrap-with-response-validation (not production-mode))
 sv/wrap-with-schema-validation
 auth/auth-middleware
 wrap-with-endpoint-identifier)

Slide 29

Slide 29 text

(defn wrap-with-operation-tracking
 "Checks for the :operation-trace metadata …"
 [handler metadata]
 (when-let [label (:operation-trace metadata)]
 (let [logger (-> metadata :ns t/get-logger)]
 (fn [request]
 (t/track* logger label (handler request))))))

Slide 30

Slide 30 text

Middleware Shootout Ring Rook No Metadata Available Has full metadata of endpoint function Must return a handler Can return handler, or return nil Interceptors execute during request dispatch Interceptors execute after dispatch (once endpoint known) Tree of contexts & interceptors Stack of interceptors unique for each endpoint

Slide 31

Slide 31 text

Goal: Efficient

Slide 32

Slide 32 text

Checklist • Sane? — Yes, natural mapping from URIs to simple functions • Smart? — Yes, metadata on functions w/ argument resolvers and middleware keeps code tight & free of globals • Fast? — Yes, efficient dispatch & interceptors

Slide 33

Slide 33 text

Links • Rook — http://github.com/AvisoNovate/rook