Thursday, 2 October 2014

Dynamic typing - Circuit breaker

Despite my fascination for Clojure I'm yet to be sold on dynamic typing. Frankly the most frustrating thing about Clojure for me is the constant struggle with the lack of static type check. What went wrong and where? There are no types to guide me nor auto-completion, and I feel I have to hold too much in my head instead of letting the IDE help me out so I can concentrate on the problem. I read quite often arguments like "dynamic typing gives you a much greater flexibility" and "the static type system is often in your way", but honestly I don't understand them. Probably because I never have done anything serious in a dynamically typed language. So I've embarked on a journey of deliberately looking for situations when dynamic typing clearly gives an advantage. Here I'd like to show the first (and so far the only) one I found. Before looking at the more complex task of writing a circuit breaker, I'd like to start with a simple one.

Logger wrapper

Imagine you'd like to log the call of some functions. In Scala or Clojure it's an easy thing to do with higher-order functions.

def logger(f: (Int, Int) => Int) =
(a: Int, b: Int) => {
println("Hello")
f.apply(a, b)
}
def add(a: Int, b: Int) = a + b
println(logger(add)(3, 4))
;Hello
;7
view raw LoggerHOF.clj hosted with ❤ by GitHub

Now this logger is able to transform any function with the (Int, Int) => Int signature to a logging one. Which is cool, but not cool enough. For every signature it has to be reimplemented. In a dynamically typed language you can use the same logger function for all. But this logger is a quite boring example, so I'd like to show something that is really useful and practical, the...

...Circuit Breaker

Imagine you application has to call a lot of web services, but the systems on the other end are quite unreliable. Sometimes they respond, but they have the habit of being unavailable for short time windows. You wouldn't like to waste time and thread calling them the n-th time if they haven't been responsive for a while. Better to wait a bit, then try again later. This is where the Circuit Breaker can help.

The Clojure version is

(defn circuit-breaker
"This higher order function will return a function with the same signature and behaviour
as the original function, but with an added functionality.
The arguments are:
f - the original function
max-try-limit - the number of times f can be called unsuccessfully before the circuit breaks
wait-seconds - how much time after the break is the circuit functioning again
fail-value - the value the function should return if the circtuit is broken"
[f max-try-limit wait-seconds fail-value]
(let [fail-count (atom 0) ; the number of failed attempts since the last reset
break-ts (atom nil) ; timestamp of last break
reached-try-limit? #(= max-try-limit @fail-count)
waited-enough? #(<
(* 1000 wait-seconds)
(- (System/currentTimeMillis) @break-ts))
reset-circuit! #(do
(reset! fail-count 0)
(reset! break-ts nil))]
(fn [& args]
(println (str "Fails: " @fail-count))
(cond
(reached-try-limit?) (cond
(waited-enough?) (do (reset-circuit!) (recur args))
:else fail-value)
:else (try ; if we are below try attempts limit
(let [result (apply f args)]
(do
(reset-circuit!) ;if the call is successful, reset the circuit counter
result))
(catch RuntimeException ex
(do
(swap! fail-count inc)
(when (reached-try-limit?)
(reset! break-ts (System/currentTimeMillis)))
fail-value)))))))

An easy way to try it out is to find a function that is simple to call with arguments that will break it, and wrap it in the circuit-breaker. The arithmetic divison is the perfect candidate.


user=> (defn div [a b]
(println "Doing it")
(/ a b))
#'user/div
user=> (def safe-div (circuit-breaker / 3 10 "Oops"))
#'user/safe-div
user=> (safe-div 6 3)
Fails: 0
2
user=> (safe-div 12 3)
Fails: 0
4
user=> (safe-div 12 0)
Fails: 0
"Oops"
user=> (safe-div 12 0)
Fails: 1
"Oops"
user=> (safe-div 12 2)
Fails: 2
6
user=> (safe-div 12 2)
Fails: 0
6
Circuit Breaker is a fascinating pattern for many reasons. First it shows a clean analog to physical engineering solutions, making software engineering look like real engineering, and also making it easy to understand. Second, from an ezoteric FP point of view, Circuit Breaker is like magical entity that swallows a "stupid" function and spits out its doppelganger, same in signature and behaviour, but with an extra "intelligence".

The reason why dynamic typing fits so well to circuit breaker, I think, is that the extra functionality it adds has nothing to do with what the function does, nor with its signature.

No comments :

Post a Comment