-
Notifications
You must be signed in to change notification settings - Fork 5
/
CircuitBreaker.hs
100 lines (84 loc) · 5.4 KB
/
CircuitBreaker.hs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
module CircuitBreaker where
import Network.HTTP.Client
import Network.HTTP.Types.Status (statusCode)
import ModelTypes
import CassandraConnector
import Control.Exception (evaluate,try,SomeException,catch)
import Control.Concurrent (MVar)
import Data.IORef
import Data.Char (digitToInt)
import Control.Monad.IO.Class (liftIO)
import Data.Time.Clock.POSIX (getCurrentTime)
import Data.Time.Clock
{-| Circuit breaker states types-}
data CircuitBreakerState
= Close {users::[User], errors:: Integer}
| Open {users::[User], time:: Integer, errors::Integer}
| HalfOpen {users::[User], errors:: Integer}
deriving (Show)
-- | Pattern matching connector state
-- ----------------------------------
{-| Circuit breaker states implementation with instruction of what to depending the state and input of the function -}
selectAllCassandraUserWithCircuitBreaker :: CircuitBreakerState -> IO CircuitBreakerState
-- | Check if we can go to the HalfOpen
selectAllCassandraUserWithCircuitBreaker (Open users time errors) = checkState (Open users time errors)
-- | We reach the maximum number of errors so we change state to open
selectAllCassandraUserWithCircuitBreaker (Close users 5) = changeStateToOpen
-- | We change state to open
selectAllCassandraUserWithCircuitBreaker (HalfOpen users 5) = changeStateToOpen
-- | Since we are [Close] state, We go to Cassandra connector to get the [User].
selectAllCassandraUserWithCircuitBreaker (Close users errors) = connectToCassandra (Close users errors) getCircuitBreakerState
-- | Since we are [HalfOpen] state, We try to go to Cassandra connector to get the [User]. if we fail again we will pass again to [Open] state
selectAllCassandraUserWithCircuitBreaker (HalfOpen users 4) = connectToCassandra (HalfOpen users 4) getCircuitBreakerState
-- | Function to connect to cassandra
-- -----------------------------------
{-| For this High Order Function we use as second argument a function [getCircuitBreakerState] which it will
return the specific state passed [Close | HalfOpen] from the invoker of the function.
To control error we use catch/handler error handling pattern-}
connectToCassandra:: CircuitBreakerState -> ([User] -> Integer -> CircuitBreakerState) -> IO CircuitBreakerState
connectToCassandra state function = do newUsers <- catch (selectAllCassandraUser) handler
state <- checkState $ function newUsers (errors state)
return state
where
handler :: SomeException -> IO [User]
handler ex = return $ [User 0 "Connnector error"]
getCircuitBreakerState :: [User] -> Integer -> CircuitBreakerState
getCircuitBreakerState users 4 = (HalfOpen users 4)
getCircuitBreakerState users errors= (Close users errors)
-- | Pattern matching circuit breaker state
-- ----------------------------------------
{-| Circuit breaker check states implementation with instruction of what to depending the state and input of the function -}
checkState :: CircuitBreakerState -> IO CircuitBreakerState
{-| Function with pattern matching to check if with [Close] state the array of users contains [User 0 "Connector error"] which means error.
Otherwise we just return the current state-}
checkState (Close [User 0 "Connnector error"] errors) = return $ Close [User 0 "Comnnector error"] $ errors + 1
checkState (Close users errors) = return $ Close users errors
{-| Function with pattern matching to check if with [HalfOpen] state the array of users contains [User 0 "Connector error"]
then we change the state to [Open] again and we reset the slice time. Otherwise we change the state to [Close]-}
checkState (HalfOpen [User 0 "Connnector error"] errors) = changeStateToOpen
checkState (HalfOpen users errors) = changeStateToClose users
{-| Function with pattern matching to check if lapsed time after open the circuit breaker has pass.
if the slice time has pass we change the state to [HalfOpen] and we try to connect again. Otherewise we keep the current state-}
checkState (Open users time errors) = do currentTime <- getCurrentTimeMillis
if currentTime > (time + 10) -- 10 is the lapsed time, we should move it to configuration param
then selectAllCassandraUserWithCircuitBreaker changeStateToHalfOpen
else return (Open users time errors)
-- | Util functions to change state of circuit breaker
-- ---------------------------------------------------
{-| We create the new state of the Circuit breaker to open-}
changeStateToOpen :: IO CircuitBreakerState
changeStateToOpen = do currentTime <- getCurrentTimeMillis
return $ Open [User 0 "Circuit breaker open, fail fast"] currentTime 0
{-| We create the new state of the Circuit breaker to open-}
changeStateToClose :: [User] -> IO CircuitBreakerState
changeStateToClose users = do return $ Close users 0
{-| We create the new state of the Circuit breaker to halfOpen-}
changeStateToHalfOpen :: CircuitBreakerState
changeStateToHalfOpen = (HalfOpen [] 4)
{-| Function to get the current time using [getCurrentTime] function and transform to int using
[floor] and [utctDayTime]-}
getCurrentTimeMillis :: IO Integer
getCurrentTimeMillis = do
currTime <- getCurrentTime
let timed = floor $ utctDayTime currTime :: Integer
return timed