packages feed

glue-core-0.7.0: src/Glue/Failover.hs

{-# LANGUAGE FlexibleContexts    #-}
{-# LANGUAGE ScopedTypeVariables #-}

-- | Module supporting failover of a service by transforming the request each time a failure occurs up to a fixed number of attempts.
module Glue.Failover
  ( FailoverOptions (..)
  , defaultFailoverOptions
  , failover
  ) where

import           Glue.Types
import           UnliftIO           (MonadUnliftIO)
import           UnliftIO.Exception (catchAny)

-- | Options for determining behaviour of failover services.
data FailoverOptions a = FailoverOptions
  { maxFailovers             :: Int
  -- ^ The maximum number of times the service will failover and make another attempt.
  , transformFailoverRequest :: a -> a
  -- ^ Each time the service performs a failover, transform the request with this function.
  }

-- | Simple defaults that result in retrying 3 times immediately without transforming the request.
defaultFailoverOptions :: FailoverOptions a
defaultFailoverOptions =
  FailoverOptions
    { maxFailovers             = 3
    , transformFailoverRequest = id
    }

-- | Creates a service that can transform requests with each subsequent failure.
failover
  :: MonadUnliftIO m
  => FailoverOptions a
  -- ^ Instance of t'FailoverOptions' to configure the failover functionality.
  -> BasicService m a b
  -- ^ The service to perform failover around.
  -> BasicService m a b
failover options service =
  let invokeService failCount request =
        let afterFail _ = invokeService (failCount + 1) (transformFailoverRequest options request)
            invoke      = service request
        in  if failCount >= maxFailovers options
              then invoke
              else catchAny invoke afterFail
  in  invokeService 0