Stuart talking about them as part of the Day of Datomic videos:
https://youtu.be/ZP-E2IgqKfA?t=2412
Simple data structure for anomalies.
(s/def ::category #{::unavailable
::interrupted
::incorrect
::forbidden
::unsupported
::not-found
::conflict
::fault
::busy})
(s/def ::message string?)
(s/def ::anomaly (s/keys :req [::category]
:opt [::message]))
Ref https://github.com/cognitect-labs/anomalies
Part of the secret sauce is that there is a finite set so you can confidently cover all cases in your code.
Table of categories and what they mean.
category | retry | fix | song |
---|---|---|---|
:unavailable | yes | make sure callee healthy | Out of Touch |
:interrupted | yes | stop interrupting | It Doesn't Matter Anymore |
:incorrect | no | fix caller bug | You'll Never Learn |
:forbidden | no | fix caller creds | I Can't Go For That |
:unsupported | no | fix caller verb | Your Imagination |
:not-found | no | fix caller noun | She's Gone |
:conflict | no | coordinate with callee | Give It Up |
:fault | no | fix callee bug | Falling |
:busy | yes | backoff and retry | Wait For Me |
Ref https://github.com/cognitect-labs/anomalies
Useful helpers for creating anomalies and testing for them...
(def ^:dynamic *default-category* ::fault)
(defn valid-category?
"Checks if given category exists in the list of categories"
{:added "0.1.0"}
[cat]
(s/valid? ::category cat))
(defn anomaly?
"Checks if given value is anomaly"
{:added "0.1.0"}
[x]
(s/valid? ::anomaly x))
(defn anomaly
"Creates new anomaly with given category(defaults to ::fault) message(optional) and data(optional)"
{:added "0.1.0"}
([] (anomaly *default-category* nil nil))
([cat-msg-data]
(cond
(valid-category? cat-msg-data) (anomaly cat-msg-data nil nil)
(string? cat-msg-data) (anomaly *default-category* cat-msg-data nil)
:else (anomaly *default-category* nil cat-msg-data)))
([cat-msg msg-data]
(cond
(and (valid-category? cat-msg) (string? msg-data)) (anomaly cat-msg msg-data nil)
(valid-category? cat-msg) (anomaly cat-msg nil msg-data)
(string? cat-msg) (anomaly *default-category* cat-msg msg-data)
:else (anomaly *default-category* cat-msg msg-data)))
([cat msg data]
{:pre [(valid-category? cat)
(or (nil? msg) (string? msg))]}
(cond-> {::category cat}
(some? msg) (assoc ::message msg)
(some? data) (assoc ::data data))))
(def busy (partial anomaly ::busy))
(def busy? #(= (::category %) ::busy))
(def conflict (partial anomaly ::conflict))
(def conflict? #(= (::category %) ::conflict))
(def fault (partial anomaly ::fault))
(def fault? #(= (::category %) ::fault))
(def forbidden (partial anomaly ::forbidden))
(def forbidden? #(= (::category %) ::forbidden))
(def incorrect (partial anomaly ::incorrect))
(def incorrect? #(= (::category %) ::incorrect))
(def interrupted (partial anomaly ::interrupted))
(def interrupted? #(= (::category %) ::interrupted))
(def not-found (partial anomaly ::not-found))
(def not-found? #(= (::category %) ::not-found))
(def unavailable (partial anomaly ::unavailable))
(def unavailable? #(= (::category %) ::unavailable))
(def unsupported (partial anomaly ::unsupported))
(def unsupported? #(= (::category %) ::unsupported))
(def message ::message)
(def data ::data)
(def category ::category)
Ref https://github.com/fmnoise/anomalies-tools
Useful helpers for translating HTTP status codes into anomalies
(def status-codes->anomalies
{403 ::forbidden
404 ::not-found
503 ::busy
504 ::unavailable})
(defn status-code->anomaly [code]
(or (get status-codes->anomalies code)
(if (<= 400 code 499)
::incorrect
::fault)))