A Swift implementation of the Circuit Breaker design pattern
This is a light weigth implementation of the Circuit Breaker design pattern done in Swift. A circuit breaker is useful for when performing some kind of work that could fail and wanting to repeat the work based on a given configuration or threshold. When the threshold is met, the circuit breaker will trip, preventing unnecessary load until the breaker resets after a timeout. This implementation provides an easy to use way of monitoring timeouts and supporting retry logic.
- iOS: 9.0 and greater
- macOS: 10.9 and greater
- Linux
You can use CocoaPods to install SGCircuitBreaker
by adding it to your Podfile
:
platform :ios, '9.0'
use_frameworks
target 'MyApp' do
pod 'SGCircuitBreaker'
end
You can use Carthage to install SGCircuitBreaker
by adding it to your Cartfile
:
github "eman6576/SGCircuitBreaker"
You can use Swift Package Manager to install SGCircuitBreaker
by adding the proper description to your Package.swift
file:
import PackageDescription
let package = Package(
name: "YOUR_PROJECT_NAME",
dependencies: [
.package(url: "https://github.com/eman6576/SGCircuitBreaker.git", .upToNextMajor(from: "1.1.4"))
],
targets: [
.target(
name: "YOUR_TARGET_NAME",
dependencies: [
"SGCircuitBreaker"
]
)
]
)
To access the available data types, import SGCircuitBreaker
into your project like so:
import SGCircuitBreaker
We can instantiate an instance of SGCircuitBreaker
in one of two ways:
let circuitBreaker = SGCircuitBreaker()
using the default configuration or like
let circuitBreaker = SGCircuitBreaker(
timeout: 20,
maxFailures: 4,
retyDelay: 3
)
With a circuit breaker instance, we need to register the work that needs to be performed:
circuitBreaker.workToPerform = { [weak self] (circuitBreaker) in
self?.mockService.call { (data, error) in
guard error == nil else {
circuitBreaker.failure(error: error)
return
}
circuitBreaker.success()
}
}
Here we register the work that needs to be performed. The work is calling an asynchronous method on mockService
that could fail. In the closure for the method call
, we check if an error occured. If it did, we report to the circuit breaker that the work failed by calling circuitBreaker.failure(error: error)
and pass the error. This will check if the maximum amount of failures have been met or not. If the maximum amount hasn't been met, then the circuit breaker would wait for a certain amount of time before trying the work again. The circuit breaker would be in the halfOpened
state. If the maximum amount of failures are met, then the circuit breaker trips. If an error didn't occur, then we report to the circuit breaker that the work was successful by calling circuitBreaker.success()
. This will reset the circuit breaker to its initial state of closed
.
Now what happens if the circuit breaker trips. We want to be able to handle this and perform any error handling logic neccessary that will not break our application. We can register how to handle the circuit breaker tripping like so:
circuitBreaker.tripped = { (circuitBreaker, error) in
print("Error occured with breaker: \(error)")
}
Here we register a handler for when the circuit breaker trips. An Error?
is passed that represents the last error that was reported. At this point, the circuit breaker is in the open
state.
There might be some cases where you need to know if the circuit breaker was successful. This also means when a success is reported to the circuit breaker. We can register a handler like so:
circuitBreaker.successful { (circuitBreaker) in
print("Circuit breaker was successful")
}
We can also handle when the circuit breaker reaches the set timeout. We can use it to cancel the registered work like so:
circuitBreaker.timedOut = { [weak self] (circuitBreaker) in
print("Timeout reached")
self?.mockService.cancel()
}
Once we have set up our handlers, we need to start the circuit breaker like so:
circuitBreaker.start()
Here is a full example of how the circuit breaker would be used:
let circuitBreaker = SGCircuitBreaker(
timeout: 20,
maxFailures: 4,
retyDelay: 3
)
circuitBreaker.workToPerform = { [weak self] (circuitBreaker) in
self?.mockService.call { (data, error) in
guard error == nil else {
circuitBreaker.failure(error: error)
return
}
circuitBreaker.success()
}
}
circuitBreaker.tripped = { (circuitBreaker, error) in
print("Error occured with breaker: \(error)")
}
circuitBreaker.successful { (circuitBreaker) in
print("Circuit breaker was successful")
}
circuitBreaker.timedOut = { [weak self] (circuitBreaker) in
print("Timeout reached")
self?.mockService.cancel()
}
circuitBreaker.start()
SGCircuitBreaker
can be configured with three parameters:
timeout
: ATimeInterval
representing how long the registered work has to finish before throwing an error. Defaults to 10.maxFailures
: AnInt
representing the number of failures allowed for retrying to performing the registered work before tripping. Defaults to 3.retryDelay
: ATimeInterval
representing how long to wait before retrying the registered work after a failure. Defailts to 2.
SGCircuitBreaker
contains some public methods and attributes:
start()
: Starts the circuit breaker.success()
: Reports to the circuit breaker that the registered work was successful.failure(error: Error? = nil)
: Reports to the circuit breaker that the registered work failed.reset()
: Resets the circuit breaker.
failureCount
: Current number of failures.state
: The current state of the circuit breaker. Can be eitheropen
,halfOpened
, orclosed
.
SGCircuitBreaker
can log to the console different events that occur within the circuit breaker. By default, this is not enabled. If you would like to enable it you can enable it when creating an instance like so:
let circuitBreaker = SGCircuitBreaker(loggingEnabled: true)
You can also change this property at anytime as well:
circuitBreaker.loggingEnabled = true
When an event is logged, this is what it would look like in the console:
SGCircuitBreaker: Registered work was successful. ๐
See SGCircuitBreakerTests.swift for some examples on how to use it.
See the contribute file!
PRs accepted.
Small note: If editing the Readme, please conform to the standard-readme specification.