Notes from my (almost) first clojure project.
~
~
~
~
~
~
~
(defmacro var-route
[route]
(if (development?)
`(var ~route)
route))
lein test
from a shell.
~
The only global variable in the application. Should hold all the data in the application.
(defn system
"Returns a new instance of the application."
[]
{:settings {:port 5000}})
(defn init
"Construct development env."
[]
(alter-var-root #'system
(constantly (-> (app/system)
(assoc :remote [6001 "localhost"])))))
(defn start
"Start all components of the application. Returns the updates system."
[system]
(when-let [level (:log-level system)]
(timbre/set-level! level))
(when-let [remote (:remote system)]
(apply client/set-remote remote))
(let [server (run-server app (:settings system))]
(timbre/info (str "Ayler started on port " (get-in system [:settings :port])))
(assoc system :stop-server-fn server)))
(defn stop
"Stops all components of the application. Returns the updated system."
[system]
(if-let [stop-server-fn (:stop-server-fn system)]
(stop-server-fn))
(assoc system :remote (client/extract-remote)))
:profiles {:dev {:source-paths ["src/dev"]
:dependencies [[ring-mock "0.1.5"]
[org.clojure/tools.namespace "0.2.4"]
[org.clojure/java.classpath "0.2.1"]]}
(ns user
(:require [ayler.app :as app]
...
[clojure.tools.namespace.find :refer (find-namespaces-in-dir)]))
(def system nil)
(defn init
...)
(defn start
...)
(defn stop
...)
(defn go
"Initialize and run"
[]
(init)
(start))
(defn reset []
(stop)
(refresh :after 'user/go))
(defn run-all-tests
...)
lein
test
)
~
user.clj
prevents the repl
from loading.
~
Hardly ever use exceptions
Indicate errors with data structures
(defn eval-on-remote-nrepl
"Evaluates the op and code on the remote nrepl.
..."
[op code]
(if (empty? @_remote)
{:status :not-connected}
(try
#_(...)
(with-open [conn (apply repl/connect @_remote)]
#_(...))
(catch java.net.ConnectException e
(do #_(...)
{:status :disconnected})))))
~
State is usually in the database or session
Every layer is in charge of it's own state
Nearly all of the functions are pure
In every layer there's a few messy functions that collect all the required state.
~
nrepl-client.clj
(defonce ^:private _remote (atom []))
;; ... getter and setter
api.clj
(defn- var-doc
"Returns the docstring of a fully qualified var name"
[namespace var]
(-> (construct-varname namespace var)
(queries/query-docstring)))
(defroutes routes
#_(...)
(GET "/api/doc/:namespace/:var" [namespace var]
(response (var-doc namespace var)))
#_(...))
Stuart Sierra - Clojure in the Large
Announced today - Component - Framework for managing lifecycle of stateful objects.
Test only the pure methods
Rely on the fact that the non-pure methods are really simple
About 0.5 to 1 test/code ratio
Compared to 1 to 1 javascript test/code ratio
Integration tests
~
Ring-json (There are more fully featured solutions - liberator)
(defroutes routes
#_(...)
(POST "/api/disconnect/" _ (response (client/disconnect))))
(def app
(-> routes
wrap-json-response
wrap-json-params))
Protection against CSRF with customized
ring-anti-forgery
(submitted PR)
~
{
"status": "done",
"response": "([x])\n Returns a number one greater than num. Does not auto-promote\n longs, will throw on overflow. See also: inc'"
}
{
"status": "error",
"response": "IllegalArgumentException No such namespace: clojure.coree clojure.lang.Var.find (Var.java:153)\n"
}
~
apiClient.handleResponse = function(response, handler) {
switch(response.status) {
case "disconnected":
$rootScope.$broadcast("connect", {disconnected: true});
case "not-connected":
$rootScope.$broadcast("connect");
case "done":
handler(response.response);
break;
case "error":
apiClient.handleError(response.response);
break;
default:
alert("Unknown response: " + response);
break;
};
};
Questions?