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.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
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
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(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.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
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