stoplight 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: c9bd1ff4a7ba5920b6b1a5e98c829b88ca495ec2
4
- data.tar.gz: 3c7698e907ec212e7072b14c9d4df4e54dd9c1b9
3
+ metadata.gz: 82aa52cf8e9479e0549ca21744fe67cbfda5aab8
4
+ data.tar.gz: fbc0abddcaa21e460473e94cd3784f6bb726c832
5
5
  SHA512:
6
- metadata.gz: 4e9fe6173c1b7524fe02ec8939512aea3c0a4cb5cf09ccf5f6cacde09ed461fc6ccfc1b4fc37eaf5008206b398d2aab9b71f5d9c1c39b94ded8ef4a57fb7b942
7
- data.tar.gz: f6a8ffb8069abad09d6a795c5acc368b6ea954e8d9d3c6359d36b61e54625923727834a49b1ac827c55cda32d2e36a9b7d16ae9fc9939cd2f35cf14870e93a7b
6
+ metadata.gz: a37ee7366b9d46a8a063eeb7f3541dfaa08e474869640993de0885779261bcda58be4e3eb7968e6d24fcb044dcd2c2e1f3edcf58e29383028340c80065d6c093
7
+ data.tar.gz: d0a1bf0cada95b072940372e030bf9007fedbed6a17ba109430a98a62a189d22552018e4685f1d5d0b20c7e4a87a02b47e94ec55abb3c4b48893237bd058b0e4
data/CHANGELOG.md CHANGED
@@ -1,5 +1,19 @@
1
1
  # Changelog
2
2
 
3
+ ## v0.2.0 (2014-08-18)
4
+
5
+ - Switched `Stoplight.data_store` and `Stoplight.notifiers` over to using
6
+ simple accessors.
7
+ - Modified `Stoplight::DataStore::Redis` to accept an instance of `Redis`.
8
+ - Refactored `Stoplight::DataStore::Redis` to use fewer keys.
9
+ - Created `Stoplight::Notifier` and subclasses.
10
+ - Sent notifications when moving from green to red.
11
+ - Renamed `Stoplight::Light::DEFAULT_THRESHOLD` to
12
+ `Stoplight::DEFAULT_THRESHOLD`.
13
+ - Renamed `Stoplight::Error::NoFallback` to `Stoplight::Error::RedLight`.
14
+ - Created `Stoplight::Mixin#stoplight` for easily creating and running simple
15
+ stoplights.
16
+
3
17
  ## v0.1.0 (2014-08-12)
4
18
 
5
19
  - Initial release.
data/README.md CHANGED
@@ -6,14 +6,17 @@
6
6
  [![Quality status][8]][9]
7
7
  [![Dependency status][10]][11]
8
8
 
9
- Traffic control for code. An implementation of the circuit breaker pattern in Ruby.
9
+ Traffic control for code. An implementation of the circuit breaker pattern in
10
+ Ruby.
11
+
12
+ Check out [stoplight-admin][12] for controlling your stoplights.
10
13
 
11
14
  ## Installation
12
15
 
13
16
  Add it to your Gemfile:
14
17
 
15
18
  ``` rb
16
- gem 'stoplight', '~> 0.1.0'
19
+ gem 'stoplight', '~> 0.2.0'
17
20
  ```
18
21
 
19
22
  Or install it manually:
@@ -22,10 +25,12 @@ Or install it manually:
22
25
  $ gem install stoplight
23
26
  ```
24
27
 
25
- This project uses [Semantic Versioning][12].
28
+ This project uses [Semantic Versioning][13].
26
29
 
27
30
  ## Setup
28
31
 
32
+ ### Data store
33
+
29
34
  Stoplight uses an in-memory data store out of the box.
30
35
 
31
36
  ``` irb
@@ -37,15 +42,39 @@ Stoplight uses an in-memory data store out of the box.
37
42
 
38
43
  If you want to use a persistent data store, you'll have to set it up. Currently
39
44
  the only supported persistent data store is Redis. Make sure you have [the Redis
40
- gem][13] installed before configuring Stoplight.
45
+ gem][14] installed before configuring Stoplight.
41
46
 
42
47
  ``` irb
43
- >> redis = Stoplight::DataStore::Redis.new(url: 'redis://127.0.0.1:6379/0')
48
+ >> redis = Redis.new(url: 'redis://127.0.0.1:6379/0')
49
+ => #<Redis:...>
50
+ >> data_store = Stoplight::DataStore::Redis.new(redis)
44
51
  => #<Stoplight::DataStore::Redis:...>
45
- >> Stoplight.data_store(redis)
52
+ >> Stoplight.data_store = data_store
46
53
  => #<Stoplight::DataStore::Redis:...>
47
54
  ```
48
55
 
56
+ ### Notifiers
57
+
58
+ Stoplight sends notifications to standard error by default.
59
+
60
+ ``` irb
61
+ >> Stoplight.notifiers
62
+ => [#<Stoplight::Notifier::StandardError:...>]
63
+ ```
64
+
65
+ If you want to send notifications elsewhere, you'll have to set them up.
66
+ Currently the only other supported notifier is HipChat. Make sure you have [the
67
+ HipChat gem][] installed before configuring Stoplight.
68
+
69
+ ``` irb
70
+ >> hipchat = HipChat::Client.new('token')
71
+ => #<HipChat::Client:...>
72
+ >> notifier = Stoplight::Notifier::HipChat.new(hipchat, 'room')
73
+ => #<Stoplight::Notifier::HipChat:...>
74
+ >> Stoplight.notifiers = [notifier])
75
+ => [#<Stoplight::Notifier::HipChat:...>]
76
+ ```
77
+
49
78
  ### Rails
50
79
 
51
80
  Stoplight is designed to work seamlessly with Rails. If you want to use the
@@ -56,7 +85,7 @@ Stoplight:
56
85
  ``` rb
57
86
  # config/initializers/stoplight.rb
58
87
  require 'stoplight'
59
- Stoplight.data_store(Stoplight::DataStore::Redis.new(...))
88
+ Stoplight.data_store = Stoplight::DataStore::Redis.new(...)
60
89
  ```
61
90
 
62
91
  ## Usage
@@ -98,11 +127,26 @@ ZeroDivisionError: divided by 0
98
127
  >> light.run
99
128
  ZeroDivisionError: divided by 0
100
129
  >> light.run
101
- Stoplight::Error::NoFallback: Stoplight::Error::NoFallback
130
+ Stoplight::Error::RedLight: Stoplight::Error::RedLight
102
131
  >> light.red?
103
132
  => true
104
133
  ```
105
134
 
135
+ When the stoplight changes from green to red, it will notify every configured
136
+ notifier.
137
+
138
+ ### Mixin
139
+
140
+ Since creating and running a stoplight is so common, we provide a mixin that
141
+ makes it easy.
142
+
143
+ ``` irb
144
+ >> include Stoplight::Mixin
145
+ => Object
146
+ >> stoplight('example-3') { 1.0 / 3 }
147
+ => 0.3333333333333333
148
+ ```
149
+
106
150
  ### Custom errors
107
151
 
108
152
  Some errors shouldn't cause your stoplight to move into the red state. Usually
@@ -110,7 +154,7 @@ these are handled elsewhere in your stack and don't represent real failures. A
110
154
  good example is `ActiveRecord::RecordNotFound`.
111
155
 
112
156
  ``` irb
113
- >> light = Stoplight::Light.new('example-3') { User.find(123) }.
157
+ >> light = Stoplight::Light.new('example-4') { User.find(123) }.
114
158
  ?> with_allowed_errors([ActiveRecord::RecordNotFound])
115
159
  => #<Stoplight::Light:...>
116
160
  >> light.run
@@ -125,12 +169,12 @@ ActiveRecord::RecordNotFound: Couldn't find User with ID=123
125
169
 
126
170
  ### Custom fallback
127
171
 
128
- Instead of raising a `Stoplight::Error::NoFallback` error when in the red state,
172
+ Instead of raising a `Stoplight::Error::RedLight` error when in the red state,
129
173
  you can provide a block to be run. This is useful when there's a good default
130
174
  value for the block.
131
175
 
132
176
  ``` irb
133
- >> light = Stoplight::Light.new('example-4') { fail }.
177
+ >> light = Stoplight::Light.new('example-5') { fail }.
134
178
  ?> with_fallback { [] }
135
179
  => #<Stoplight::Light:...>
136
180
  >> light.run
@@ -149,13 +193,13 @@ Some bits of code might be allowed to fail more or less frequently than others.
149
193
  You can configure this by setting a custom threshold in seconds.
150
194
 
151
195
  ``` irb
152
- >> light = Stoplight::Light.new('example-5') { fail }.
196
+ >> light = Stoplight::Light.new('example-6') { fail }.
153
197
  ?> with_threshold(1)
154
198
  => #<Stoplight::Light:...>
155
199
  >> light.run
156
200
  RuntimeError:
157
201
  >> light.run
158
- Stoplight::Error::NoFallback: Stoplight::Error::NoFallback
202
+ Stoplight::Error::RedLight: Stoplight::Error::RedLight
159
203
  ```
160
204
 
161
205
  ### Rails
@@ -178,12 +222,12 @@ end
178
222
 
179
223
  ## Credits
180
224
 
181
- Stoplight is brought to you by [@camdez][14] and [@tfausak][15] from [@OrgSync][16]. We were
182
- inspired by Martin Fowler's [CircuitBreaker][17] article.
225
+ Stoplight is brought to you by [@camdez][15] and [@tfausak][16] from [@OrgSync][17]. We were
226
+ inspired by Martin Fowler's [CircuitBreaker][18] article.
183
227
 
184
228
  If this gem isn't cutting it for you, there are a few alternatives, including:
185
- [circuit_b][18], [circuit_breaker][19], [simple_circuit_breaker][20], and
186
- [ya_circuit_breaker][21].
229
+ [circuit_b][19], [circuit_breaker][20], [simple_circuit_breaker][21], and
230
+ [ya_circuit_breaker][22].
187
231
 
188
232
  [1]: https://github.com/orgsync/stoplight
189
233
  [2]: https://badge.fury.io/rb/stoplight.svg
@@ -196,13 +240,15 @@ If this gem isn't cutting it for you, there are a few alternatives, including:
196
240
  [9]: https://codeclimate.com/github/orgsync/stoplight
197
241
  [10]: https://gemnasium.com/orgsync/stoplight.svg
198
242
  [11]: https://gemnasium.com/orgsync/stoplight
199
- [12]: http://semver.org/spec/v2.0.0.html
200
- [13]: https://rubygems.org/gems/redis
201
- [14]: https://github.com/camdez
202
- [15]: https://github.com/tfausak
203
- [16]: https://github.com/OrgSync
204
- [17]: http://martinfowler.com/bliki/CircuitBreaker.html
205
- [18]: https://github.com/alg/circuit_b
206
- [19]: https://github.com/wsargent/circuit_breaker
207
- [20]: https://github.com/soundcloud/simple_circuit_breaker
208
- [21]: https://github.com/wooga/circuit_breaker
243
+ [12]: https://github.com/orgsync/stoplight-admin
244
+ [13]: http://semver.org/spec/v2.0.0.html
245
+ [14]: https://rubygems.org/gems/redis
246
+ [the hipchat gem]: https://rubygems.org/gems/hipchat
247
+ [15]: https://github.com/camdez
248
+ [16]: https://github.com/tfausak
249
+ [17]: https://github.com/OrgSync
250
+ [18]: http://martinfowler.com/bliki/CircuitBreaker.html
251
+ [19]: https://github.com/alg/circuit_b
252
+ [20]: https://github.com/wsargent/circuit_breaker
253
+ [21]: https://github.com/soundcloud/simple_circuit_breaker
254
+ [22]: https://github.com/wooga/circuit_breaker
data/lib/stoplight.rb CHANGED
@@ -8,10 +8,21 @@ require 'stoplight/data_store/redis'
8
8
  require 'stoplight/error'
9
9
  require 'stoplight/failure'
10
10
  require 'stoplight/light'
11
+ require 'stoplight/mixin'
12
+ require 'stoplight/notifier'
13
+ require 'stoplight/notifier/base'
14
+ require 'stoplight/notifier/hip_chat'
15
+ require 'stoplight/notifier/standard_error'
11
16
 
12
17
  module Stoplight
13
18
  # @return [Gem::Version]
14
- VERSION = Gem::Version.new('0.1.0')
19
+ VERSION = Gem::Version.new('0.2.0')
20
+
21
+ # @return [Integer]
22
+ DEFAULT_THRESHOLD = 3
23
+
24
+ @data_store = DataStore::Memory.new
25
+ @notifiers = [Notifier::StandardError.new]
15
26
 
16
27
  class << self
17
28
  extend Forwardable
@@ -20,8 +31,10 @@ module Stoplight
20
31
  attempts
21
32
  clear_attempts
22
33
  clear_failures
34
+ delete
23
35
  failures
24
36
  names
37
+ purge
25
38
  record_attempt
26
39
  record_failure
27
40
  set_state
@@ -29,13 +42,11 @@ module Stoplight
29
42
  state
30
43
  )
31
44
 
32
- # @param data_store [DataStore::Base]
33
45
  # @return [DataStore::Base]
34
- def data_store(data_store = nil)
35
- @data_store = data_store if data_store
36
- @data_store = DataStore::Memory.new unless defined?(@data_store)
37
- @data_store
38
- end
46
+ attr_accessor :data_store
47
+
48
+ # @return [Array<Notifier::Base>]
49
+ attr_accessor :notifiers
39
50
 
40
51
  # @param name [String]
41
52
  # @return [Boolean]
@@ -59,7 +70,7 @@ module Stoplight
59
70
  # @param name [String]
60
71
  # @return [Integer]
61
72
  def threshold(name)
62
- data_store.threshold(name) || Light::DEFAULT_THRESHOLD
73
+ data_store.threshold(name) || DEFAULT_THRESHOLD
63
74
  end
64
75
  end
65
76
  end
@@ -8,6 +8,16 @@ module Stoplight
8
8
  fail NotImplementedError
9
9
  end
10
10
 
11
+ # Deletes all green lights without failures.
12
+ def purge
13
+ fail NotImplementedError
14
+ end
15
+
16
+ # @param _name [String]
17
+ def delete(_name)
18
+ fail NotImplementedError
19
+ end
20
+
11
21
  # @param _name [String]
12
22
  # @param _error [Exception]
13
23
  def record_failure(_name, _error)
@@ -76,20 +86,24 @@ module Stoplight
76
86
  fail ArgumentError, 'Invalid state'
77
87
  end
78
88
 
79
- def key(name, slug)
80
- [DataStore::KEY_PREFIX, name, slug].join(':')
89
+ def attempts_key
90
+ key('attempts')
91
+ end
92
+
93
+ def failures_key(name)
94
+ key('failures', name)
81
95
  end
82
96
 
83
- def attempt_key(name)
84
- key(name, 'attempts')
97
+ def states_key
98
+ key('states')
85
99
  end
86
100
 
87
- def failure_key(name)
88
- key(name, 'failures')
101
+ def thresholds_key
102
+ key('thresholds')
89
103
  end
90
104
 
91
- def settings_key(name)
92
- key(name, 'settings')
105
+ def key(slug, name = nil)
106
+ [KEY_PREFIX, name, slug].compact.join(':')
93
107
  end
94
108
  end
95
109
  end
@@ -8,59 +8,84 @@ module Stoplight
8
8
  end
9
9
 
10
10
  def names
11
- @data.keys.map do |key|
12
- match = /^#{DataStore::KEY_PREFIX}:(.+):([^:]+)$/.match(key)
13
- match[1] if match
14
- end.compact.uniq
11
+ all_thresholds.keys
15
12
  end
16
13
 
17
- def record_failure(name, error)
18
- failure = Failure.new(error)
19
- array = @data[failure_key(name)] ||= []
20
- array.push(failure)
14
+ def purge
15
+ names
16
+ .select { |l| failures(l).empty? }
17
+ .each { |l| delete(l) }
21
18
  end
22
19
 
23
- def clear_failures(name)
24
- @data.delete(failure_key(name))
20
+ def delete(name)
21
+ clear_attempts(name)
22
+ clear_failures(name)
23
+ all_states.delete(name)
24
+ all_thresholds.delete(name)
25
25
  end
26
26
 
27
- def failures(name)
28
- @data[failure_key(name)] || []
29
- end
27
+ # @group Attempts
30
28
 
31
- def settings(name)
32
- @data[settings_key(name)] ||= {}
29
+ def attempts(name)
30
+ all_attempts[name] || 0
33
31
  end
34
32
 
35
- def threshold(name)
36
- settings(name)['threshold']
33
+ def record_attempt(name)
34
+ all_attempts[name] ||= 0
35
+ all_attempts[name] += 1
37
36
  end
38
37
 
39
- def set_threshold(name, threshold)
40
- settings(name)['threshold'] = threshold
38
+ def clear_attempts(name)
39
+ all_attempts.delete(name)
41
40
  end
42
41
 
43
- def record_attempt(name)
44
- key = attempt_key(name)
45
- @data[key] ||= 0
46
- @data[key] += 1
42
+ # @group Failures
43
+
44
+ def failures(name)
45
+ @data[failures_key(name)] || []
47
46
  end
48
47
 
49
- def clear_attempts(name)
50
- @data.delete(attempt_key(name))
48
+ def record_failure(name, error)
49
+ (@data[failures_key(name)] ||= []).push(Failure.new(error))
51
50
  end
52
51
 
53
- def attempts(name)
54
- @data[attempt_key(name)] || 0
52
+ def clear_failures(name)
53
+ @data.delete(failures_key(name))
55
54
  end
56
55
 
56
+ # @group State
57
+
57
58
  def state(name)
58
- settings(name)['state'] || DataStore::STATE_UNLOCKED
59
+ all_states[name] || STATE_UNLOCKED
59
60
  end
60
61
 
61
62
  def set_state(name, state)
62
63
  validate_state!(state)
63
- settings(name)['state'] = state
64
+ all_states[name] = state
65
+ end
66
+
67
+ # @group Threshold
68
+
69
+ def threshold(name)
70
+ all_thresholds[name]
71
+ end
72
+
73
+ def set_threshold(name, threshold)
74
+ all_thresholds[name] = threshold
75
+ end
76
+
77
+ private
78
+
79
+ def all_attempts
80
+ @data[attempts_key] ||= {}
81
+ end
82
+
83
+ def all_states
84
+ @data[states_key] ||= {}
85
+ end
86
+
87
+ def all_thresholds
88
+ @data[thresholds_key] ||= {}
64
89
  end
65
90
  end
66
91
  end