Liking cljdoc? Tell your friends :D

resilience-clj

A general-purpose resilience library for safely calling unreliable external resources (HTTP APIs, databases, message queues, etc.).

Plain data, higher-order function pattern, natural composition via comp / ->.

Dependency

;; project.clj
[org.clojars.konkon/resilience-clj "0.1.0"]

Circuit Breaker

(require '[resilience-clj.circuit-breaker :as cb])

;; Create
(def breaker (cb/create {:failure-threshold 5
                         :reset-timeout-ms  10000
                         :half-open-calls   1
                         :failure?          (fn [result]
                                              (and (map? result)
                                                   (>= (:status result) 500)))
                         :on-state-change   (fn [old new]
                                              (println "Circuit:" old "->" new))}))

;; Wrap a function
(def safe-call (cb/wrap breaker http-get))

;; Use
(safe-call "https://api.example.com")
;; => On success: returns http-get result as-is
;; => When open:  {:resilience/rejected :circuit-open}

;; Check status
(cb/status breaker)  ;; => :closed | :open | :half-open

;; Manual reset
(cb/reset! breaker)

;; Shutdown (stops go-loop)
(cb/shutdown! breaker)

Retry

(require '[resilience-clj.retry :as retry])

;; Synchronous
(def resilient-call
  (retry/wrap http-get {:max-attempts   3
                        :backoff-ms     1000
                        :backoff-factor 2
                        :jitter         0.1
                        :retry?         (fn [r] (>= (:status r) 500))}))

(resilient-call "https://api.example.com")

;; Asynchronous (returns a core.async channel)
(require '[clojure.core.async :as async])

(def async-call
  (retry/wrap-async http-get {:max-attempts 3 :backoff-ms 500}))

(async/<!! (async-call "https://api.example.com"))

Timeout

(require '[resilience-clj.timeout :as timeout])

(def safe-call (timeout/wrap http-get {:timeout-ms 3000}))

(safe-call "https://api.example.com")
;; => On success: returns http-get result as-is
;; => On timeout: {:resilience/rejected :timeout}

Rejection Response

All modules express rejection using the unified format {:resilience/rejected <reason>}.

(require '[resilience-clj.rejected :as rejected])

(rejected/rejected? {:resilience/rejected :timeout})    ;; => true
(rejected/reason {:resilience/rejected :circuit-open})  ;; => :circuit-open
(rejected/reject :custom-reason)                        ;; => {:resilience/rejected :custom-reason}

Composition

;; Circuit Breaker + Retry
(def breaker (cb/create {:failure-threshold 5 :reset-timeout-ms 10000}))

(def resilient-call
  (-> http-get
      (retry/wrap {:max-attempts 3 :backoff-ms 500})
      (->> (cb/wrap breaker))))

(resilient-call "https://api.example.com")
;; retry is applied first; if it still fails, circuit breaker counts the failure

;; Timeout + Retry + Circuit Breaker
(def resilient-call
  (-> http-get
      (timeout/wrap {:timeout-ms 3000})
      (retry/wrap {:max-attempts 3 :backoff-ms 500 :retry? rejected/rejected?})
      (->> (cb/wrap breaker))))

Design Principles

  • Plain data (no defrecord)
  • Function in, function out (higher-order function pattern)
  • Each pattern is independently usable
  • Composition via comp or -> feels natural
  • State is managed explicitly with controllable lifecycle

License

MIT

Can you improve this documentation?Edit on GitHub

cljdoc builds & hosts documentation for Clojure/Script libraries

Keyboard shortcuts
Ctrl+kJump to recent docs
Move to previous article
Move to next article
Ctrl+/Jump to the search field
× close