/cats-circuit-breaker

https://ayushworks.github.io/cats-circuit-breaker/

Primary LanguageScalaApache License 2.0Apache-2.0

cats-circuit-breaker

Build Status

cats-circuit-breaker is a purely functional circuit breaker. It allows you to decorate services with a circuit breaker.

Design principles

cats-circuit-breaker is an count based circuit breaker. The core idea is to wrap a protected function call in a circuit breaker, which monitors for failures. When the failures reach a certain threshold, the circuit breaker trips, and all further calls to the circuit breaker return with an error upto a waitDuration.

For now failures is same as exceptions. This could be optimised in future to allow you to define what a failure is.

The library uses types from cats-effect for managing asynchronous, concurrent mutable references.

cats-circuit-breaker is completely pure, which allows for ease of reasoning and composability. The library is inspired by SystemFw's Upperbound library and by his talks on how to manage shared state in pure FP.

Usage

CircuitBreaker

The protagonist of the library is a CircuitBreaker, which is defined as:

trait CircuitBreaker[F[_]] {

  def run[A](body: => A): F[A]

  def runF[A](fa: F[A]): F[A]
}

The run method takes a thunk of A, which can represent any program, and returns an F[A] that represents the action of wrapping it within the circuit breaker.

The runF method takes an F[A], which can represent any program, and returns an F[A] that represents the action of wrapping it within the circuit breaker.

The CircuitBreaker trait is the core component of the library and allows you to protect a service from being overloaded in case of recurring failures.

CircuitBreakerConfig
case class CircuitBreakerConfig(failureRateThreshold: Double, windowSize: Int, minimumNumberOfCalls: Int, waitDurationInOpenState: Duration)
  • failureRateThreshold : the failure rate threshold in percentage. Circuit breaker is open when this rate is exceeded.
  • windowSize: the size of the window which is used to monitor the outcome of underlying service when circuit breaker is closed.
  • minimumNumberOfCalls: minimum number of calls which are required (per sliding window period) before the CircuitBreaker can calculate the error rate.
  • waitDurationInOpenState: The time that the CircuitBreaker should wait before transitioning from open to closed.
Creating a CircuitBreaker

To create a CircuitBreaker, use the create method:

object CircuitBreaker {
  def create[F[_]: Sync](implicit config: CircuitBreakerConfig): F[CircuitBreaker[F]]
}

create creates a new CircuitBreaker.

Wrapping underlying services

The underlying service which is being protected could be in two flavours - embellished or bare.

  • protecting an embellished service requires us to use protectF method
def getData[F[_]](id: Int): F[User]

def protectedGetData[F](id: Int): F[User] = {
  import circuitbreaker.protectF 
  protectF(getData(id), circuitBreaker)
}
    
  • protecting a bare service requires us to use protect method
def getData(id: Int): User

def protectedGetData[F](id: Int)(circuitBreaker: CircuitBreaker[F]): F[User] = {
  import circuitbreaker.protect 
  protect(getData(id), circuitBreaker)
}
    
Using the same breaker for multiple services

We can also use the same circuit breaker for monitoring multiple services if required.

    
    for {
      breaker <- CircuitBreaker.create(CircuitBreakerConfig.defaultConfig)
      user <- breaker.run(userService.getUser(id))
      socialInfo <- breaker.run(socialService.getInfo(user))
      ..
    }

In the above example , the userService and socialService calls are monitored by the same instance of circuit breaker.

Whats next

  • Add support for events on circuit breaker state changes
  • Allow you to configure what constitues a failure
  • Provide support for quality based circuit state eg..slow response times
  • Allow ignorable exceptions