On Github ordnungswidrig / clojure-conj-2015-i-did-the-api-wrong
clojure/conj Philadelphia, PA 2015
Philipp Meier – mailto:philipp@meier.name – @ordnungswprog
you can create quite some mess
_
That escalated quickly
Liberator knobs _
You must guide your users
_
(defresource frobnicator [db id] :exists (fn [ctx] {::frob (load-from-db db id)}) :e-tag (fn [{frob ::frob}] (:_version frob)) :handle-not-found (fn [ctx] (format "Frob not found with id %s" id)) :handle-ok (fn [{frob ::frob} frob]))
(defresource frobnicator [db id] :exists? (fn [ctx] {::frob (load-from-db db id)}) :e-tag (fn [{frob ::frob}] (:_version frob)) :handle-not-found (fn [ctx] (format "Frob not found with id %s" id)) :handle-ok (fn [{frob ::frob} frob]))
…do not compose too well
(defn load-entity [db & {:as opts}] ...)
(def default-opts {:eager true :keywordize false :fetch-depth 5})
(defn load-customer [db id] (apply load-entity db (merge default-ops :query {:id id}))
;; => IllegalArgumentException No value supplied for key: [:fetch-depth 5] clojure.lang.PersistentHashMap.create (PersistentHashMap.java:77)
(apply load-entity my-db (apply concat (merge default-ops :query {:id id}))
(->> some-opts (apply concat) (apply load-entitiy))
_
(defresource foo :handle-ok "ok")
(def defaults {:exists? (fn [_] (zero? (rand-int 1)))})
(apply defresource bar (merge defaults {:handle-ok "ok"}))
;; => CompilerException java.lang.RuntimeException: Can't take value of a macro: #'liberator.core/defresource, compiling:(/private/var/folders/p1/47jm9vq12g93ry9vf525h7vh0000gn/T/form-init2406362871536982238.clj:1:1)
find backward compatible way
optional map as first argument
(defresource bar defaults {:handle-ok "ok"})
better stay away from macros
(def bar (resource defaults :handle-ok "ok"))
(def bar (resource (merge defaults {:handle-ok "ok"}))
(fn [ctx] (ring-response "response-body" {:headers {"X-Foo" "bar"}))))
But this is what you need to do
(fn [ctx] ;; workaround until issue #152 is fixed (-> "response-body" (as-response (assoc-in ctx [:representation :media-type] "text/plain")) (assoc-in [:headers "X-Foo"] "bar") (ring-response))))
(defn print-my-stuff [stuff] (println (str "Stuff: " (.toString stuff))
;; => NullPointerException clojure.lang.Reflector.invokeNoArgInstanceMember (Reflector.java:301)
Reflector.java?
500 INTERNAL SERVER ERROR
;; since 0.12.0 (defresource foo :handle-exception (fn [{e :exception :as context}] ...) ;; hurray ...)
State
State?
State!
State is part of the API
(def system-state (atom {})
(def another-state (atom {})
(def auxiliary-state (atom {})
(def state-manager-state (atom {})
(def yetanother-state (atom {})
(def state (atom {:system-state {} :another-state {} :auxiliary-state {} :yetanother-state {}})
*-in is your friend
(defn start-ingest [state file] (start-ingest-in-background file) (assoc-in state [:system-state :ingest :status] :running)) (swap! state start-ingest file)
(def ^:dynamic db) (with-db (connect some-url) (fetch-from-db some-db))
(defn exec [db query] ...) (defn fetch [db entity id] (exec db (build-query entity id))) (defn load-user [db id] (fetch db :user id)) (defn authorized? [db id role] (let [user (load-user db id)] ...))
You cannot bind a dynamic var to two values
I want to talk about fixing all this on a more general level
Not function docstrings
;; request {:request-method :get :headers {...}}
;; response {:status 418 :body "I'm a teapot" :headers {...}}
;; request handler (fn [req] {:status 418 :body "I'm a teapot" :headers {...}})
;; this is passed as the context {:status ... :request ... :representation ... :resource ... :whatever-you-want ...}
;; every decision function (fn [ctx] (do-something) {:foo "some value"} ;; context update ;; context is "merged" with context update
(Insert applause jingle here)
_
(defprotocol Resource (exists? [ctx] "returns true when the resource currently exists") (existed? [ctx] "returns true when the resoues previosly existed") (handle-ok [ctx] "returns the representation of resource entity") ...)
Yada
(defrecord StringResource [s last-modified] Representations (representations [_] [{:media-type "text/plain" :charset platform-charsets}]) ResourceModification (last-modified [_ _] last-modified) ResourceVersion (version [_ _] s) Get (GET [_ _] s))
om-next does this
nice experiment take the users perspective
WORKSASDESIGNED - learn why a use wants this
Trapdoor: English Lock at en.wikipedia [CC BY-SA 3.0 (http://creativecommons.org/licenses/by-sa/3.0)], from Wikimedia Commons https://upload.wikimedia.org/wikipedia/commons/f/fb/Trapdoor.jpg