- Day 2
- 2.a Standard - Indentation
- 2.b Standard - Namespace
- 2.c Standard - Naming
- 2.d Standard - Function
- 2.e Standard - Threading
- 3. Eco-system - Clojure eco-system
- 4.a Url Shortener
- 5.a Backend - Initiate a clojure app project
- 5.c Backend - State management
- 6.a API - Web server
- 6.b API - Manual routing
- 6.c API - Router
- 6.d API - Encode/Decode
- 7.a Persistence - Inmem repository
Body indentation
;; good
(when something
(something-else))
(with-out-str
(println "Hello, ")
(println "world!"))
;; bad - four spaces
(when something
(something-else))
;; bad - one space
(with-out-str
(println "Hello, ")
(println "world!"))
Function Argument alignment
;; good
(filter even?
(range 1 10))
;; bad - argument aligned with function name (one space indent)
(filter even?
(range 1 10))
;; bad - two space indent
(filter even?
(range 1 10))
Function Argument Indentation
;; good
(filter
even?
(range 1 10))
;; bad - two-space indent
(filter
even?
(range 1 10))
Binding Alignment
;; good
(let [thing1 "some stuff"
thing2 "other stuff"]
(foo thing1 thing2))
;; bad
(let [thing1 "some stuff"
thing2 "other stuff"]
(foo thing1 thing2))
Gather Trailing Parentheses
;; good; single line
(when something
(something-else))
;; bad; distinct lines
(when something
(something-else)
)
Empty lines between Top-Level Forms
;; good
(def x ...)
(defn foo ...)
;; bad
(def x ...)
(defn foo ...)
;; Exception for grouping of related def’s together
;; good
(def min-rows 10)
(def max-rows 20)
(def min-cols 15)
(def max-cols 30)
No blank lines within definition forms
;; okay - the line break delimits a cond pair
(defn fibo-iter
([n] (fibo-iter 0 1 n))
([curr nxt n]
(cond
(zero? n)
curr
:else
(recur nxt (+ curr nxt) (dec n)))))
;; bad
(defn fibo-iter
([n] (fibo-iter 0 1 n))
([curr nxt n]
(cond
(zero? n) curr
:else (recur nxt (+ curr nxt) (dec n)))))
Avoid single-segment namespaces (conflict risks)
;; good
(ns example.ns)
;; bad
(ns example)
Use lisp-case for function and variable names
;; good
(def some-var ...)
(defn some-fun ...)
;; bad
(def someVar ...)
(defn somefun ...)
(def some_fun ...)
Use CapitalCase for protocols, records, structs and types
;; good
(defprotocol Moveable ...)
(defrecord Car ...)
;; bad
(defprotocol moveable ...)
(defrecord car ...)
The names of predicate functions (returning a boolean) should end in a question mark
;; good
(defn palindrome? ...)
;; bad
(defn palindrome-p ...) ; Common Lisp style
(defn is-palindrome ...) ; Java style
Use earmuffs for things intended for rebinding
;; good
(def ^:dynamic *a* 10)
;; bad
(def ^:dynamic a 10)
Don’t use a special notation for constants; everything is assumed a constant
;; good
(def max-size 10)
;; bad
(def MAX-SIZE 10) ; Java style
(def +max-size+ 10) ; Common Lisp style, global constant
(def *max-size* 10) ; Common Lisp style, global variable
Use ‘_’ for destructuing targets and formal argument names whose value will be ignored by the body form
;; good
(let [[a b _ c] [1 2 3 4]]
(println a b c))
(dotimes [_ 3]
(println "Hello!"))
;; bad
(let [[a b c d] [1 2 3 4]]
(println a b d))
(dotimes [i 3]
(println "Hello!"))
For documenting-purpose, you can explicit a name for unused arguments or maps by prepending a ‘_’
;; good
(defn myfun1 [context _]
(assoc context :foo "bar"))
(defn myfun2 [context {:keys [id]}]
(assoc context :user-id id))
;; better
(defn myfun1 [context _user]
(assoc context :foo "bar"))
(defn myfun2 [context {:keys [id] :as _user}]
(assoc context :user-id id))
Separate function name and argument from function body
;; good
(defn foo "My foo documentation"
[x]
(bar x))
;; good
(defn foo [x]
(bar x))
;; bad
(defn foo
[x] (bar x))
Indent each arity form of a function definition vertically aligned with its parameters
;; good
(defn foo
"I have two arities."
([x]
(foo x 1))
([x y]
(+ x y)))
;; bad - extra indentation
(defn foo
"I have two arities."
([x]
(foo x 1))
([x y]
(+ x y)))
Sort arity of a function from fewest to most arguments
;; good - it's easy to scan for the nth arity
(defn foo
"I have two arities."
([x]
(foo x 1))
([x y]
(+ x y)))
;; bad
(defn foo
([x] 1)
([x y z] (foo x (foo y z)))
([x y] (+ x y))
([w x y z & more] (reduce foo (foo w (foo x (foo y z))) more)))
Don’t define ns vars inside functions
;; very bad
(defn foo []
(def x 5)
#_body)
Don’t shadow clojure.core names with local bindings
;; bad - clojure.core/map must be fully qualified inside the function
(defn foo [map]
#_body)
Don’t shadow clojure.core names with local bindings
;; good
(-> [1 2 3]
reverse
(conj 4)
prn)
;; not as good
(prn (conj (reverse [1 2 3])
4))
- HTTP server: http-kit-server, ring
- State management: component, integrant, mount
- Data Driven Schemas: malli , clojure.spec, Schemas
- Data Transformation: meander, clara-rules
- Rule engine: clara-rules
- Test lifecycle management: kaocha, test-runner
- Clojure Project lifecycle: Leiningen, boot, tools.deps
- Configuration Management: aero, environ, config
- Web Router: reitit, yada
- Time: tick, clj-time
- JDBC-based access: nextjdbc, honeysql, hugSQL
- Kafka: java-kafka, jackdaw
- Async programming: core.async, manifold
- Testing library: midje, testit
Feature: Url shortened creation
Scenario: A user creates his shortened url
Given a user "dpanza"
When "dpanza" creates a short url for "https://clojure.org/"
Then the short url is stored
And a short url is given to user
Feature: Manage url shortened
Scenario: A user lists all the urls created
Given a user "dpanza"
And existing shortened urls
| user | link | shortened | visits |
| dpanza | https://clojure.org/ | clj | 12 |
When "dpanza" gets his urls shortened
Then the returned list of url shortened return "1" results
Then the list contains link for "https://clojure.org/" shortened to "clj" visited by "12" users
Feature: Redirect visitors
Scenario: A visitor clicks a shortened url
Given existing shortened urls
| user | link | shortened | visits |
| dpanza | https://clojure.org/ | clj | 12 |
When a visitor clicks on a shortened url "clj"
Then the visitor is redirected to "https://clojure.org/"
And the number of visits for the shortened url "clj" of "dpanza" is incremented
# Create a project using tools-deps
clojure -Ttools install com.github.seancorfield/clj-new '{:git/tag "v1.2.404"}' :as clj-new
clojure -Tclj-new app :name defsquare/training
ls –l
CHANGELOG.md
LICENSE
README.md
build.clj
deps.edn
doc
pom.xml
resources
src
test
https://github.com/tolitius/mount
in your deps.edn file, add mount
{:deps {mount/mount {:mvn/version "0.1.17"}}}
in a clojure namespace
(defstate my-state
:start (do (prn "starting") {:status :run})
:stop (do (prn "stopping") {:status :stopped}))
(defn -main [& args] (mount.core/start))
(comment
(main)
(mount.core/start)
(mount.core/stop))
https://github.com/ring-clojure/ring
in your deps.edn file, add mount
{:deps {ring/ring-core {:mvn/version "1.10.0"}
ring/ring-jetty-adapter {:mvn/version "1.10.0"}}}
;; in a clojure namespace
(defn handler [_request]
{:status 200
:headers {"Content-Type" "text/html"}
:body "Hello World"})
(defstate server
:start (run-jetty handler {:port 8080 :join? false})
:stop (.stop server))
(defmulti router (fn [{:keys [uri request-method] :as _request}]
[request-method uri]))
(defmethod router [:get "/api/entity"]
[_request]
{:status 200
:headers {"Content-Type" "text/html"}
:body "This is an entity!"})
(defmethod router :default
[{:keys [uri]}]
{:status 404
:headers {"Content-Type" "text/html"}
:body (str "Request '" uri "' not found")})
(defstate server
:start (run-jetty router {:port 8080 :join? false})
:stop (.stop server))
https://github.com/metosin/reitit
in your deps.edn file
{:deps {metosin/reitit {:mvn/version "0.7.0-alpha5"}}}
in a clojure namespace
(def app
(ring/ring-handler
(ring/router ["/api"
["/entity" {:get (fn [_request]
{:status 200
:headers {"Content-Type" "text/html"}
:body "This is an entity!"})}]])))
(comment
(app {:request-method :get, :uri "/api/entity"}))
(:require
[muuntaja.core :as m]
[reitit.ring.coercion :as coercion]
[reitit.ring.middleware.muuntaja :as muuntaja]
[reitit.ring.middleware.parameters :as parameters])
(def app
(ring/ring-handler
(ring/router
["/api"
["/entity/:id"
{:post
{:handler
(fn [request]
(let [{:keys [body-params path-params query-params]} request]
{:status 200 :body ""}))}}]]
{:data {:muuntaja m/instance
:middleware [muuntaja/format-middleware
coercion/coerce-exceptions-middleware
coercion/coerce-request-middleware
coercion/coerce-response-middleware
parameters/parameters-middleware]}})))
(def initial-state {:users {}})
(def db (atom initial-state))
@db
;; => {:users {}}
(swap! db assoc-in [:users :dpanza] {:foo "bar"})
;; => {:users {:dpanza {:foo "bar"}}}
@db
;; => {:users {:dpanza {:foo "bar"}}}
(reset! db initial-state)
;; => {:users {}}
@db
;; => {:users {}}