Skip to content

Commit

Permalink
Merge pull request #32 from IBM-Swift/develop
Browse files Browse the repository at this point in the history
A few semantic improvements
  • Loading branch information
tfrank64 authored Jul 6, 2017
2 parents 9e8bc70 + 8aad93f commit b306c07
Show file tree
Hide file tree
Showing 4 changed files with 98 additions and 88 deletions.
39 changes: 22 additions & 17 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ To leverage the CircuitBreaker package in your Swift application, you should spe
...

dependencies: [
.Package(url: "https://github.com/IBM-Swift/CircuitBreaker.git", majorVersion: 1),
.Package(url: "https://github.com/IBM-Swift/CircuitBreaker.git", majorVersion: 2),

...

Expand Down Expand Up @@ -65,10 +65,11 @@ func myFunction(a: Int, b: Int) -> Int {
* Must specify the fallback function, and the endpoint to circuit break
* Optional configurations include: timeout, resetTimeout, maxFailures, and bulkhead
```swift
let breaker = CircuitBreaker(fallback: myFallback, command: myFunction)
let breaker = CircuitBreaker(command: myFunction, fallback: myFallback)
```

4. Invoke the call to the function by calling the CircuitBreaker `run()` function and pass the corresponding arguments:
4. Invoke the call to the function by calling the CircuitBreaker `run()` method. You should pass the corresponding arguments for the command and fallback closures. In this sample, `myFunction` takes two integers as parameters while `myFallback` takes a string as its second parameter:

```swift
breaker.run(commandArgs: (a: 10, b: 20), fallbackArgs: (msg: "Something went wrong."))
```
Expand Down Expand Up @@ -96,7 +97,7 @@ func myFunction(a: Int, b: Int) -> Int {
return value
}

let breaker = CircuitBreaker(fallback: myFallback, command: myFunction)
let breaker = CircuitBreaker(command: myFunction, fallback: myFallback)

breaker.run(commandArgs: (a: 10, b: 20), fallbackArgs: (msg: "Something went wrong."))
breaker.run(commandArgs: (a: 15, b: 35), fallbackArgs: (msg: "Something went wrong."))
Expand All @@ -118,9 +119,10 @@ func myFallback(err: BreakerError, msg: String) {
}
```

2. Create a function wrapper for the logic you intend to circuit break (this allows you to alert the CircuitBreaker of a failure or a success):
2. Create a context function for the logic you intend to circuit break (this allows you to alert the CircuitBreaker of a failure or a success). Please note that a context function receives an `Invocation` object as its parameter. An instance of the `Invocation` class states 1) the parameter types that must be passed to the context function, 2) the return type from the execution of the context function, and 3) parameter type used as the second argument for the fallback closure:

```swift
func myWrapper(invocation: Invocation<(String), Void, String>) {
func myContextFunction(invocation: Invocation<(String), Void, String>) {
let requestParam = invocation.commandArgs
// Create HTTP request
guard let url = URL(string: "http://mysever.net/path/\(requestParam)") else {
Expand Down Expand Up @@ -158,16 +160,18 @@ func myWrapper(invocation: Invocation<(String), Void, String>) {
}
```

3. Create a CircuitBreaker instance for each function (e.g. endpoint) you wish to circuit break:
3. Create a CircuitBreaker instance for each context function (e.g. endpoint) you wish to circuit break:
* Must specify the fallback function and the endpoint to circuit break
* Optional configurations include: timeout, resetTimeout, maxFailures, rollingWindow, and bulkhead
```swift
let breaker = CircuitBreaker(fallback: myFallback, commandWrapper: myWrapper)
let breaker = CircuitBreaker(contextCommand: myContextFunction, fallback: myFallback)
```

4. Invoke the call to the endpoint by calling the CircuitBreaker `run()` function and pass any arguments:
4. Invoke the call to the endpoint by calling the CircuitBreaker `run()` method. You should pass the corresponding arguments for the context command and fallback closures. In this sample, `myContextFunction` takes a string as its parameter while `myFallback` takes a string as its second parameter:

```swift
breaker.run(commandArgs: "92827", fallbackArgs: (msg: "Something went wrong."))
let id: String = ...
breaker.run(commandArgs: id, fallbackArgs: (msg: "Something went wrong."))
```

Full Implementation:
Expand All @@ -182,7 +186,7 @@ func myFallback(err: BreakerError, msg: String) {
Log.verbose("Message: \(msg)")
}

func myWrapper(invocation: Invocation<(String), Void, String>) {
func myContextFunction(invocation: Invocation<(String), Void, String>) {
let requestParam = invocation.commandArgs
// Create HTTP request
guard let url = URL(string: "http://mysever.net/path/\(requestParam)") else {
Expand Down Expand Up @@ -219,9 +223,10 @@ func myWrapper(invocation: Invocation<(String), Void, String>) {
}.resume()
}

let breaker = CircuitBreaker(fallback: myFallback, commandWrapper: myWrapper)
let breaker = CircuitBreaker(contextCommand: myContextFunction, fallback: myFallback)

breaker.run(commandArgs: "92827", fallbackArgs: (msg: "Something went wrong."))
let id: String = ...
breaker.run(commandArgs: id, fallbackArgs: (msg: "Something went wrong."))

...
```
Expand All @@ -231,12 +236,12 @@ breaker.run(commandArgs: "92827", fallbackArgs: (msg: "Something went wrong."))

#### Basic Usage Constructor
```swift
CircuitBreaker(timeout: Int = 1000, resetTimeout: Int = 60000, maxFailures: Int = 5, rollingWindow: Int = 10000, bulkhead: Int = 0, callback: @escaping AnyFallback<C>, command: @escaping AnyFunction<A, B>)
CircuitBreaker(timeout: Int = 1000, resetTimeout: Int = 60000, maxFailures: Int = 5, rollingWindow: Int = 10000, bulkhead: Int = 0, command: @escaping AnyFunction<A, B>, fallback: @escaping AnyFallback<C>)
```

#### Advanced Usage Constructor
```swift
CircuitBreaker(timeout: Int = 1000, resetTimeout: Int = 60000, maxFailures: Int = 5, rollingWindow: Int = 10000, bulkhead: Int = 0, callback: @escaping AnyFallback<C>, commandWrapper: @escaping AnyFunctionWrapper<A, B>)
CircuitBreaker(timeout: Int = 1000, resetTimeout: Int = 60000, maxFailures: Int = 5, rollingWindow: Int = 10000, bulkhead: Int = 0, contextCommand: @escaping AnyContextFunction<A, B>, fallback: @escaping AnyFallback<C>)
```

#### Constructor parameters
Expand All @@ -247,13 +252,13 @@ CircuitBreaker(timeout: Int = 1000, resetTimeout: Int = 60000, maxFailures: Int
* `bulkhead` Number of the limit of concurrent requests running at one time. Default is set to 0, which is equivalent to not using the bulkheading feature.
* `fallback` Function user specifies to signal timeout or fastFail completion. Required format: `(BreakerError, (fallbackArg1, fallbackArg2,...)) -> Void`
* `command` Function to circuit break (basic usage constructor).
* `commandWrapper` Invocation wrapper around logic to circuit break, allows user defined failures (provides reference to circuit breaker instance; advanced usage constructor).
* `contextCommand` Contextual function to circuit break, which allows user defined failures (the context provides an indirect reference to the corresponding circuit breaker instance; advanced usage constructor).

### Stats
```swift
...
// Create CircuitBreaker
let breaker = CircuitBreaker(fallback: myFallback, command: myFunction)
let breaker = CircuitBreaker(command: myFunction, fallback: myFallback)

// Invoke breaker call
breaker.run(commandArgs: (a: 10, b: 20), fallbackArgs: (msg: "Something went wrong."))
Expand Down
25 changes: 15 additions & 10 deletions Sources/CircuitBreaker.swift
Original file line number Diff line number Diff line change
Expand Up @@ -29,17 +29,22 @@ public enum BreakerError {
case fastFail
}

/// CircuitBreaker class
///
/// A - Parameter types used in the arguments for the command closure.
/// B - Return type from the execution of the command closure.
/// C - Parameter type used as the second argument for the fallback closure.
public class CircuitBreaker<A, B, C> {
// Closure aliases
public typealias AnyFunction<A, B> = (A) -> (B)
public typealias AnyFunctionWrapper<A, B> = (Invocation<A, B, C>) -> B
public typealias AnyContextFunction<A, B> = (Invocation<A, B, C>) -> B
public typealias AnyFallback<C> = (BreakerError, C) -> Void

private(set) var state: State = State.closed
private let failures: FailureQueue
private let command: AnyFunction<A, B>?
private let fallback: AnyFallback<C>
private let commandWrapper: AnyFunctionWrapper<A, B>?
private let contextCommand: AnyContextFunction<A, B>?
private let bulkhead: Bulkhead?

public let timeout: Int
Expand All @@ -54,24 +59,24 @@ public class CircuitBreaker<A, B, C> {

private let queue = DispatchQueue(label: "Circuit Breaker Queue", attributes: .concurrent)

private init(timeout: Int, resetTimeout: Int, maxFailures: Int, rollingWindow: Int, bulkhead: Int, fallback: @escaping AnyFallback<C>, command: (AnyFunction<A, B>)?, commandWrapper: (AnyFunctionWrapper<A, B>)?) {
private init(timeout: Int, resetTimeout: Int, maxFailures: Int, rollingWindow: Int, bulkhead: Int, command: (AnyFunction<A, B>)?, contextCommand: (AnyContextFunction<A, B>)?, fallback: @escaping AnyFallback<C>) {
self.timeout = timeout
self.resetTimeout = resetTimeout
self.maxFailures = maxFailures
self.rollingWindow = rollingWindow
self.fallback = fallback
self.command = command
self.commandWrapper = commandWrapper
self.contextCommand = contextCommand
self.failures = FailureQueue(size: maxFailures)
self.bulkhead = (bulkhead > 0) ? Bulkhead.init(limit: bulkhead) : nil
}

public convenience init(timeout: Int = 1000, resetTimeout: Int = 60000, maxFailures: Int = 5, rollingWindow: Int = 10000, bulkhead: Int = 0, fallback: @escaping AnyFallback<C>, command: @escaping AnyFunction<A, B>) {
self.init(timeout: timeout, resetTimeout: resetTimeout, maxFailures: maxFailures, rollingWindow: rollingWindow, bulkhead: bulkhead, fallback: fallback, command: command, commandWrapper: nil)
public convenience init(timeout: Int = 1000, resetTimeout: Int = 60000, maxFailures: Int = 5, rollingWindow: Int = 10000, bulkhead: Int = 0, command: @escaping AnyFunction<A, B>, fallback: @escaping AnyFallback<C>) {
self.init(timeout: timeout, resetTimeout: resetTimeout, maxFailures: maxFailures, rollingWindow: rollingWindow, bulkhead: bulkhead, command: command, contextCommand: nil, fallback: fallback)
}

public convenience init(timeout: Int = 1000, resetTimeout: Int = 60000, maxFailures: Int = 5, rollingWindow: Int = 10000, bulkhead: Int = 0, fallback: @escaping AnyFallback<C>, commandWrapper: @escaping AnyFunctionWrapper<A, B>) {
self.init(timeout: timeout, resetTimeout: resetTimeout, maxFailures: maxFailures, rollingWindow: rollingWindow, bulkhead: bulkhead, fallback: fallback, command: nil, commandWrapper: commandWrapper)
public convenience init(timeout: Int = 1000, resetTimeout: Int = 60000, maxFailures: Int = 5, rollingWindow: Int = 10000, bulkhead: Int = 0, contextCommand: @escaping AnyContextFunction<A, B>, fallback: @escaping AnyFallback<C>) {
self.init(timeout: timeout, resetTimeout: resetTimeout, maxFailures: maxFailures, rollingWindow: rollingWindow, bulkhead: bulkhead, command: nil, contextCommand: contextCommand, fallback: fallback)
}

// Run
Expand Down Expand Up @@ -141,7 +146,7 @@ public class CircuitBreaker<A, B, C> {

let _ = command(commandArgs)
complete(error: false)
} else if let commandWrapper = self.commandWrapper {
} else if let contextCommand = self.contextCommand {
let invocation = Invocation(breaker: self, commandArgs: commandArgs)

setTimeout() { [weak invocation] in
Expand All @@ -151,7 +156,7 @@ public class CircuitBreaker<A, B, C> {
}
}

let _ = commandWrapper(invocation)
let _ = contextCommand(invocation)
}
}

Expand Down
Loading

0 comments on commit b306c07

Please sign in to comment.