Add anomalies
as a dependency in your deps.edn
file:
{:deps
{net.clojars.luchiniatwork/anomalies {:mvn/version "0.0.2"}}}
Require anomalies
where needed:
(require '[anomalies.core :as anom])
Throw with anom/throw
instead:
(anom/throw "Oops")
Explore thrown exception with ex-data
:
(ex-data e) ;;=> #:anomalies.core{:message "Oops,
:category :anomalies.core/unavailable,
:retry-possible? true,
:fix :make-sure-callee-healthy}
anom/throw-anom
supports defaults to ::anom/unavailable
. You can
specify a map with your intended category as a second param (the first
being a message). The second param map can also have a fully qualified
keyword as an ::anom/id
for this anomaly.
Anomalies capture errors as information that is simple, actionable, generic, and extensible.
- Simple: Anomalies contain only information about the error, not e.g. flow control or causality.
- Actionable: the
::anom/category
aims to be a top-level partitioning of all kinds of errors, allowing many programs that need to branch during error-handling to branch only on this keyword. - Generic: Anomalies are represented as ordinary maps, and can be created and consumed without any special API.
- Extensible: As maps are open, applications can add their own context
via namespaced keywords. That said, try to do as much as possible
by dispatching only via
::anom/category
.
Anomalies overlap in purpose with e.g. Java exceptions and HTTP responses. The differences are instructive:
-
Java exceptions are not simple, as they combine error information with a flow control mechanism. This is a problem in e.g. async applications and transductions. Both of these contexts need to talk about errors, but do not want to utilize exceptions' flow control by stack unwinding.
-
HTTP responses provide useful partitioning via status codes. But since status codes appear inside a server response they cannot possibly cover e.g. failure to get a response at all.
The category column contains the name of an anomaly category within
the cognitect.anomalies
namespace.
The retry column is "yes" if a replay of the same activity might reasonably lead to a different outcome. When a program encounters a retryable anomaly, it may be reasonable to back off and try again.
The "fix" column provides an example of how a programmer or operator might fix problems in this category.
The "song" column contains a Hall and Oates song. The idea that Hall and Oates are software gurus is controversial in some circles, so you can treat this as flavor text.
category | retry-possible? | fix | song |
---|---|---|---|
:unavailable | true | make sure callee healthy | Out of Touch |
:interrupted | true | stop interrupting | It Doesn't Matter Anymore |
:incorrect | false | fix caller bug | You'll Never Learn |
:forbidden | false | fix caller creds | I Can't Go For That |
:unsupported | false | fix caller verb | Your Imagination |
:not-found | false | fix caller noun | She's Gone |
:conflict | false | coordinate with callee | Give It Up |
:fault | false | fix callee bug | Falling |
:busy | true | backoff and retry | Wait For Me |
Besides the usage described on the Getting Started
session with anom/throw-anom
you can also create a valid anomaly
with the function anom/->Anom
:
(throw (ex-info "Oops" (-> (anom/->Anom "Oops" {::anom/category ::anom/not-found})
(assoc :my-app/details "Resource not found!"))))
Or maybe just "copy-and-paste plus some utility functions." This
project draws largely from
cognitect.anomalies. cognitect.anomalies
is a great idea and I noticed I used the pattern over and over without
ever bringing it as a dependency because it's so light as to provide
just a spec.
In practice I caught myself writing the same simple utility functions for several projects so decided to wrap them around my own flavor of anomalies.
If you find a bug, submit a GitHub issue.
This project is looking for team members who can help this project succeed! If you are interested in becoming a team member please open an issue.
Copyright © 2021 Tiago Luchini
Distributed under the MIT License.