Skip to content

Commit 60fdbf2

Browse files
authored
Merge pull request #31 from IBM-Swift/develop
Develop
2 parents 9599300 + d7042b3 commit 60fdbf2

File tree

9 files changed

+1260
-1344
lines changed

9 files changed

+1260
-1344
lines changed

.gitignore

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
11
.build/*
2-
Packages/*
32
CircuitBreaker.xcodeproj
43
*.DS_Store

Package.swift

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import PackageDescription
22

33
let package = Package(
4-
name: "CircuitBreaker",
5-
dependencies: [
6-
.Package(url: "https://github.com/IBM-Swift/HeliumLogger.git", majorVersion: 1),
7-
]
4+
name: "CircuitBreaker",
5+
dependencies: [
6+
.Package(url: "https://github.com/IBM-Swift/LoggerAPI.git", majorVersion: 1),
7+
]
88
)

README.md

Lines changed: 117 additions & 136 deletions
Original file line numberDiff line numberDiff line change
@@ -28,221 +28,200 @@ To leverage the CircuitBreaker package in your Swift application, you should spe
2828
...
2929

3030
dependencies: [
31-
.Package(url: "https://github.com/IBM-Swift/CircuitBreaker.git", majorVersion: 0),
31+
.Package(url: "https://github.com/IBM-Swift/CircuitBreaker.git", majorVersion: 1),
3232

3333
...
3434

3535
])
3636
```
3737

38-
### Basic Usage:
38+
### Basic Usage
3939

40-
*The CircuitBreaker state is based on timeouts only.*
40+
*In this form of usage, the CircuitBreaker state is based on timeouts only.*
41+
42+
If the function you are circuit breaking makes an asynchronous call(s) and the execution time of that call should be taking into account, then see [`Advanced Usage`](#advanced-usage) below.
4143

4244
1. Define a fallback function with the signature `(<BreakerError, (fallbackArg1, fallbackArg2,...)>) -> Void`:
4345
```swift
44-
func testFallback (error: Bool, msg: String) {
46+
func myFallback (error: Bool, msg: String) {
4547
// The fallback will be called if the request does not return before the specified timeout
4648
// or if the CircuitBreaker is currently in Open state and set to fail fast.
47-
// Client is expected to use the fallback to do alternate processing, such as show an error page.
49+
// Client code can use the fallback function to do alternate processing, such as show an error page.
4850
Log.verbose("Error: \(error)")
4951
Log.verbose("Message: \(msg)")
5052
}
5153
```
5254

53-
2. Create an endpoint to circuit break:
55+
2. Create a function to circuit break:
5456
```swift
55-
func testEndpoint(input: String, completion: @escaping (JSON, Bool) -> ()) {
56-
// Create Request
57-
var req = URLRequest(url: URL(string: "http://testAPIRoute/\(input)")!)
58-
req.httpMethod = "GET"
59-
req.allHTTPHeaderFields = ["Content-Type": "application/json"]
60-
61-
let session = URLSession.shared
62-
63-
// Perform Request
64-
session.dataTask(with: req) {result, res, err in
65-
guard let result = result else {
66-
let json = JSON("Error: No results.")
67-
completion(json, true)
68-
return
69-
}
70-
71-
// Convert results to a JSON object
72-
let json = JSON(data: result)
73-
74-
completion(json, false)
75-
}.resume()
57+
func myFunction(a: Int, b: Int) -> Int {
58+
// do stuff
59+
let value: Int = ...
60+
return value
7661
}
7762
```
7863

7964
3. Create a CircuitBreaker instance for each endpoint you wish to circuit break:
8065
* Must specify the fallback function, and the endpoint to circuit break
8166
* Optional configurations include: timeout, resetTimeout, maxFailures, and bulkhead
8267
```swift
83-
let breaker = CircuitBreaker(fallback: testFallback, command: testEndpoint)
68+
let breaker = CircuitBreaker(fallback: myFallback, command: myFunction)
8469
```
8570

86-
4. Invoke the call to the endpoint by calling the CircuitBreaker `run()` function and pass any arguments:
71+
4. Invoke the call to the function by calling the CircuitBreaker `run()` function and pass the corresponding arguments:
8772
```swift
88-
breaker.run(commandArgs: (input : "testInput1", { data, err in
89-
if err {
90-
print(err)
91-
} else {
92-
print(data)
93-
}
94-
}), fallbackArgs: (msg: "Something went wrong."))
73+
breaker.run(commandArgs: (a: 10, b: 20), fallbackArgs: (msg: "Something went wrong."))
9574
```
9675

9776
* May be called multiple times with varied input:
9877
```swift
99-
breaker.run(commandArgs: (input : "testInput2", { data, err in
100-
if err {
101-
print(err)
102-
} else {
103-
print(data)
104-
}
105-
}), fallbackArgs: (msg: "Something went wrong."))
78+
breaker.run(commandArgs: (a: 15, b: 35), fallbackArgs: (msg: "Something went wrong."))
10679
```
10780

10881
Full Implementation:
10982
```swift
11083
...
11184

112-
func testFallback (error: Bool, msg: String) {
85+
func myFallback (error: Bool, msg: String) {
11386
// The fallback will be called if the request does not return before the specified timeout
11487
// or if the CircuitBreaker is currently in Open state and set to fail fast.
115-
// Client is expected to use the fallback to do alternate processing, such as show an error page.
88+
// Client code can use the fallback function to do alternate processing, such as show an error page.
11689
Log.verbose("Error: \(error)")
11790
Log.verbose("Message: \(msg)")
11891
}
11992

120-
func testEndpoint(input: String, completion: @escaping (JSON, Bool) -> ()) {
121-
// Create Request
122-
var req = URLRequest(url: URL(string: "http://testAPIRoute/\(input)")!)
123-
req.httpMethod = "GET"
124-
req.allHTTPHeaderFields = ["Content-Type": "application/json"]
125-
126-
let session = URLSession.shared
127-
128-
// Perform Request
129-
session.dataTask(with: req) {result, res, err in
130-
guard let result = result else {
131-
let json = JSON("Error: No results.")
132-
completion(json, true)
133-
return
134-
}
135-
136-
// Convert results to a JSON object
137-
let json = JSON(data: result)
138-
139-
completion(json, false)
140-
}.resume()
93+
func myFunction(a: Int, b: Int) -> Int {
94+
// do stuff
95+
let value: Int = ...
96+
return value
14197
}
14298

143-
let breaker = CircuitBreaker(fallback: testFallback, command: testEndpoint)
144-
145-
breaker.run(commandArgs: (input : "testInput1", { data, err in
146-
if err {
147-
print(err)
148-
} else {
149-
print(data)
150-
}
151-
}), fallbackArgs: (msg: "Something went wrong."))
99+
let breaker = CircuitBreaker(fallback: myFallback, command: myFunction)
152100

153-
breaker.run(commandArgs: (input : "testInput2", { data, err in
154-
if err {
155-
print(err)
156-
} else {
157-
print(data)
158-
}
159-
}), fallbackArgs: (msg: "Something went wrong."))
101+
breaker.run(commandArgs: (a: 10, b: 20), fallbackArgs: (msg: "Something went wrong."))
102+
breaker.run(commandArgs: (a: 15, b: 35), fallbackArgs: (msg: "Something went wrong."))
160103

161104
...
162105
```
163106

164-
### Advanced Usage:
107+
### Advanced Usage
165108

166-
*The CircuitBreaker state is based on timeouts and user defined failures.*
109+
*In this form of usage, the CircuitBreaker state is based on timeouts and user defined failures (quite useful when the function you are circuit breaking makes an asynchronous call).*
167110

168111
1. Define a fallback function with the signature `(<BreakerError, (fallbackArg1, fallbackArg2,...)>) -> Void`:
169112
```swift
170-
func testFallback (err: BreakerError, msg: String) {
113+
func myFallback (err: BreakerError, msg: String) {
171114
// The fallback will be called if the request does not return before the specified timeout
172115
// or if the CircuitBreaker is currently in Open state and set to fail fast.
173-
// Client is expected to use the fallback to do alternate processing, such as show an error page.
174116
Log.verbose("Error: \(error)")
175117
Log.verbose("Message: \(msg)")
176118
}
177119
```
178120

179-
2. Create an endpoint to circuit break:
121+
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):
180122
```swift
181-
func sum(a: Int, b: Int) -> (Int) {
182-
print(a + b)
183-
return a + b
184-
}
185-
```
123+
func myWrapper(invocation: Invocation<(String), Void, String>) {
124+
let requestParam = invocation.commandArgs
125+
// Create HTTP request
126+
guard let url = URL(string: "http://mysever.net/path/\(requestParam)") else {
127+
// Something went wrong...
186128

187-
3. Create an Invocation wrapper of the endpoint you wish to circuit break:
188-
* This allows the user to define and alert the CircuitBreaker of a failure
189-
```swift
190-
func sumWrapper(invocation: Invocation<(Int, Int), Int>) -> Int {
191-
let result = sum(a: invocation.args.0, b: invocation.args.1)
192-
if result != 7 {
193-
invocation.notifyFailure()
194-
return 0
195-
} else {
196-
invocation.notifySuccess()
197-
return result
129+
...
130+
131+
invocation.notifyFailure()
132+
}
133+
134+
var req = URLRequest(url: url)
135+
req.httpMethod = "GET"
136+
req.allHTTPHeaderFields = ["Content-Type": "application/json"]
137+
let session = URLSession.shared
138+
139+
// Perform Request
140+
session.dataTask(with: req) { result, res, err in
141+
guard let result = result else {
142+
// Failed getting a result from the server
143+
144+
...
145+
146+
invocation.notifyFailure()
147+
return
198148
}
149+
150+
// Convert results to a JSON object
151+
let json = JSON(data: result)
152+
// Process JSON data
153+
154+
...
155+
156+
invocation.notifySuccess()
157+
}.resume()
199158
}
200159
```
201160

202-
4. Create a CircuitBreaker instance for each endpoint you wish to circuit break:
203-
* Must specify the fallback function, and the endpoint to circuit break
204-
* Optional configurations include: timeout, resetTimeout, maxFailures, and bulkhead
161+
3. Create a CircuitBreaker instance for each function (e.g. endpoint) you wish to circuit break:
162+
* Must specify the fallback function and the endpoint to circuit break
163+
* Optional configurations include: timeout, resetTimeout, maxFailures, rollingWindow, and bulkhead
205164
```swift
206-
let breakerAdvanced = CircuitBreaker(fallback: testCallback, commandWrapper: sumWrapper)
165+
let breaker = CircuitBreaker(fallback: myFallback, commandWrapper: myWrapper)
207166
```
208167

209-
5. Invoke the call to the endpoint by calling the CircuitBreaker `run()` function and pass any arguments:
168+
4. Invoke the call to the endpoint by calling the CircuitBreaker `run()` function and pass any arguments:
210169
```swift
211-
breakerAdvanced.run(commandArgs: (a: 3, b: 4), fallbackArgs: (msg: "Something went wrong."))
170+
breaker.run(commandArgs: "92827", fallbackArgs: (msg: "Something went wrong."))
212171
```
213172

214173
Full Implementation:
215174

216175
```swift
217176
...
218177

219-
func testFallback (err: BreakerError, msg: String) {
178+
func myFallback (err: BreakerError, msg: String) {
220179
// The fallback will be called if the request does not return before the specified timeout
221180
// or if the CircuitBreaker is currently in Open state and set to fail fast.
222-
// Client is expected to use the fallback to do alternate processing, such as show an error page.
223181
Log.verbose("Error: \(error)")
224182
Log.verbose("Message: \(msg)")
225183
}
226184

227-
func sum(a: Int, b: Int) -> (Int) {
228-
print(a + b)
229-
return a + b
230-
}
185+
func myWrapper(invocation: Invocation<(String), Void, String>) {
186+
let requestParam = invocation.commandArgs
187+
// Create HTTP request
188+
guard let url = URL(string: "http://mysever.net/path/\(requestParam)") else {
189+
// Something went wrong...
190+
191+
...
231192

232-
func sumWrapper(invocation: Invocation<(Int, Int), Int>) -> Int {
233-
let result = sum(a: invocation.args.0, b: invocation.args.1)
234-
if result != 7 {
235-
invocation.notifyFailure()
236-
return 0
237-
} else {
238-
invocation.notifySuccess()
239-
return result
193+
invocation.notifyFailure()
194+
}
195+
196+
var req = URLRequest(url: url)
197+
req.httpMethod = "GET"
198+
req.allHTTPHeaderFields = ["Content-Type": "application/json"]
199+
let session = URLSession.shared
200+
201+
// Perform Request
202+
session.dataTask(with: req) { result, res, err in
203+
guard let result = result else {
204+
// Failed getting a result from the server
205+
206+
...
207+
208+
invocation.notifyFailure()
209+
return
240210
}
211+
212+
// Convert results to a JSON object
213+
let json = JSON(data: result)
214+
// Process JSON data
215+
216+
...
217+
218+
invocation.notifySuccess()
219+
}.resume()
241220
}
242221

243-
let breakerAdvanced = CircuitBreaker(fallback: testCallback, commandWrapper: sumWrapper)
222+
let breaker = CircuitBreaker(fallback: myFallback, commandWrapper: myWrapper)
244223

245-
breakerAdvanced.run(commandArgs: (a: 3, b: 4), fallbackArgs: (msg: "Something went wrong."))
224+
breaker.run(commandArgs: "92827", fallbackArgs: (msg: "Something went wrong."))
246225

247226
...
248227
```
@@ -252,34 +231,36 @@ breakerAdvanced.run(commandArgs: (a: 3, b: 4), fallbackArgs: (msg: "Something we
252231

253232
#### Basic Usage Constructor:
254233
```swift
255-
CircuitBreaker(timeout: Double = 10, resetTimeout: Int = 60, maxFailures: Int = 5, bulkhead: Int = 0, callback: @escaping AnyFallback<C>, command: @escaping AnyFunction<A, B>)
234+
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>)
256235
```
257-
* `timeout` Amount in seconds that the request should complete before. Default is set to 10 seconds.
258-
* `resetTimeout` Amount in seconds to wait before setting to halfopen state. Default is set to 60 seconds.
259-
* `maxFailures` Number of failures allowed before setting state to open. Default is set to 5.
236+
* `timeout` Amount in milliseconds that your function should complete before the invocation is considered a failure. Default is set to 1000 milliseconds.
237+
* `resetTimeout` Amount in milliseconds to wait before setting to halfopen state. Default is set to 60000 milliseconds.
238+
* `maxFailures` Number of failures allowed within `rollingWindow` before setting state to open. Default is set to 5.
239+
* `rollingWindow` Time window in milliseconds where the maximum number of failures must occur to trip the circuit. For instance, say `maxFailures` is 5 and `rollingWindow` is 10000 milliseconds. In such case, for the circuit to trip, 5 invocation failures must occur in a time window of 10 seconds, even if these failures are not consecutive. Default is set to 10000 milliseconds.
260240
* `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.
261241
* `fallback` Function user specifies to signal timeout or fastFail completion. Required format: `(BreakerError, (fallbackArg1, fallbackArg2,...)) -> Void`
262-
* `command` Endpoint name to circuit break.
242+
* `command` Function to circuit break.
263243

264244
#### Advanced Usage Constructor:
265245
```swift
266-
CircuitBreaker(timeout: Double = 10, resetTimeout: Int = 60, maxFailures: Int = 5, bulkhead: Int = 0, callback: @escaping AnyFallback<C>, commandWrapper: @escaping AnyFunctionWrapper<A, B>)
246+
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>)
267247
```
268-
* `timeout` Amount in seconds that the request should complete before. Default is set to 10 seconds.
248+
* `timeout` Amount in seconds that the request should complete before the invocation is considered a failure. Default is set to 1 second.
269249
* `resetTimeout` Amount in seconds to wait before setting to halfopen state. Default is set to 60 seconds.
270-
* `maxFailures` Number of failures allowed before setting state to open. Default is set to 5.
250+
* `maxFailures` Number of failures allowed within `rollingWindow` before setting state to open. Default is set to 5.
251+
* `rollingWindow` Time window in milliseconds where the maximum number of failures must occur to trip the circuit. For instance, say `maxFailures` is 5 and `rollingWindow` is 10000 milliseconds. In such case, for the circuit to trip, 5 invocation failures must occur in a time window of 10 seconds, even if these failures are not consecutive. Default is set to 10000 milliseconds.
271252
* `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.
272253
* `fallback` Function user specifies to signal timeout or fastFail completion. Required format: `(BreakerError, (fallbackArg1, fallbackArg2,...)) -> Void`
273-
* `commandWrapper` Invocation wrapper around endpoint name to circuit break, allows user defined failures.
254+
* `commandWrapper` Invocation wrapper around logic to circuit break, allows user defined failures (provides reference to circuit breaker instance).
274255

275256
### Stats
276257
```swift
277258
...
278259
// Create CircuitBreaker
279-
let breaker = CircuitBreaker(fallback: tesFallback, command: testEndpoint)
260+
let breaker = CircuitBreaker(fallback: myFallback, command: myFunction)
280261

281262
// Invoke breaker call
282-
breaker.run(args: (input: "test"))
263+
breaker.run(commandArgs: (a: 10, b: 20), fallbackArgs: (msg: "Something went wrong."))
283264

284265
// Log Stats snapshot
285266
breaker.snapshot()

0 commit comments

Comments
 (0)