Skip to content

Repository files navigation

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.2.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)

Notes:

  • :failure-threshold counts consecutive failures — a success while :closed resets the counter.
  • :half-open does not limit concurrent probe calls; :half-open-calls only sets how many successes are needed to close.

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"))

Notes:

  • wrap-async runs f on a dedicated thread (async/thread), so blocking functions (HTTP calls etc.) are safe to wrap.
  • When retries are exhausted by exceptions, the last exception object is put on the channel as a value (not re-thrown) — check with (instance? Throwable result).

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}

Notes:

  • Exceptions thrown by f are re-thrown as-is (ex-data preserved).
  • On timeout the underlying future is cancelled via thread interrupt; if f ignores interruption, its thread keeps running until f returns.

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

About

No description or website provided.

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages