Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Remove the AASM dependency #22

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
*.gem
.bundle
Gemfile.lock
gemfiles/aasm.gemfile.lock
pkg
3 changes: 3 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
language: ruby
rvm:
- 2.0.0
gemfile:
- Gemfile
- gemfiles/aasm.gemfile
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ An implementation of Michael Nygard's Circuit Breaker pattern.

## REQUIREMENTS:

circuit_breaker has a dependency on AASM @ https://github.com/rubyist/aasm/tree/master
None. circuit_breaker no longer depends on [AASM](https://github.com/rubyist/aasm/tree/master) but will raise AASM::InvalidTransition if that exception is defined.

## INSTALL:

Expand Down
2 changes: 0 additions & 2 deletions circuit_breaker.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -59,8 +59,6 @@ Gem::Specification.new do |s|
s.extra_rdoc_files = ["History.txt", "README.txt"]
s.rdoc_options = ["--main", "README.txt", "--charset=UTF-8"]

s.add_runtime_dependency "aasm"

s.add_development_dependency "rake"
s.add_development_dependency "rspec"
end
3 changes: 3 additions & 0 deletions gemfiles/aasm.gemfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
eval_gemfile "../Gemfile"

gem "aasm"
1 change: 1 addition & 0 deletions lib/circuit_breaker.rb
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@ def circuit_handler_class(klass = nil)

require 'circuit_breaker/circuit_handler'
require 'circuit_breaker/circuit_broken_exception'
require 'circuit_breaker/errors'
require 'circuit_breaker/circuit_state'

require 'circuit_breaker/trip_checker/count'
Expand Down
97 changes: 62 additions & 35 deletions lib/circuit_breaker/circuit_state.rb
Original file line number Diff line number Diff line change
@@ -1,47 +1,14 @@
require 'aasm'

#
# CircuitState is created individually for each object, and keeps
# track of how the object is doing and whether the object's circuit
# has tripped or not.
#
class CircuitBreaker::CircuitState

include AASM

aasm.state :half_open

aasm.state :open

aasm.state :closed, :enter => :reset_failure_count

aasm.initial_state :closed

#
# Trips the circuit breaker into the open state where it will immediately fail.
#
aasm.event :trip do
transitions :to => :open, :from => [:closed, :half_open]
end

#
# Transitions from an open state to a half_open state.
#
aasm.event :attempt_reset do
transitions :to => :half_open, :from => [:open]
end

#
# Close the circuit from an open or half open state.
#
aasm.event :reset do
transitions :to => :closed, :from => [:open, :half_open]
end

def initialize()
def initialize
@failure_count = 0
@last_failure_time = nil
@call_count = 0
@state = :closed
end

attr_accessor :last_failure_time
Expand All @@ -67,4 +34,64 @@ def reset_counts
reset_failure_count
@call_count = 0
end

def current_state
@state
end

# define #open?, :closed?, :half_open?
STATES = %i(open half_open closed).each do |state|
define_method("#{state}?") do
@state == state
end
end

#the state transition map, to: [:from]
TRANSITIONS = {
trip: { open: [:closed, :half_open] },
attempt_reset: { half_open: [:open] },
reset: { closed: [:open, :half_open] },
}

#
# Trips the circuit breaker into the open state where it will immediately fail.
#
def trip
fail invalid_transition_exception("trip") unless can_transition_to?(:trip, :open, current_state)
@state = :open
end
alias trip! trip

#
# Transitions from an open state to a half_open state.
#
def attempt_reset
fail invalid_transition_exception("attempt_reset") unless can_transition_to?(:attempt_reset, :half_open, current_state)
@state = :half_open
end

#
# Close the circuit from an open or half open state.
#
def reset
fail invalid_transition_exception("reset") unless can_transition_to?(:reset, :closed, current_state)
@state = :closed
reset_failure_count
end

private

attr_reader :state

def can_transition_to?(transition, to_state, from_state)
TRANSITIONS.fetch(transition).fetch(to_state).include?(from_state)
end

def invalid_transition_exception(event_name)
invalid_transition_exception_class.new(self, event_name, "circuit_state")
end

def invalid_transition_exception_class
CircuitBreaker::InvalidTransition
end
end
22 changes: 22 additions & 0 deletions lib/circuit_breaker/errors.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
module CircuitBreaker
begin
require 'aasm'
InvalidTransition = ::AASM::InvalidTransition
rescue LoadError, NameError
InvalidTransition = Class.new(RuntimeError) do
attr_reader :object, :event_name, :state_machine_name, :failures

def initialize(object, event_name, state_machine_name, failures = [])
@object, @event_name, @state_machine_name, @failures = object, event_name, state_machine_name, failures
end

def message
"Event '#{event_name}' cannot transition from '#{object.aasm(state_machine_name).current_state}'. #{reasoning}"
end

def reasoning
"Failed callback(s): #{@failures}." unless failures.empty?
end
end
end
end
2 changes: 1 addition & 1 deletion lib/circuit_breaker/version.rb
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
module CircuitBreaker
VERSION = '1.1.2'
VERSION = '1.2.0'
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✂️

end
100 changes: 50 additions & 50 deletions spec/circuit_breaker_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,56 +11,56 @@ class TestClass
include CircuitBreaker

def initialize()
@failure = false
end

def fail!
@failure = true
end

def succeed!
@failure = false
end

def call_external_method()
if @failure == true
raise "FAIL"
end

"hello world!"
end

def second_method()
raise 'EPIC FAIL'
end

def unresponsive_method
sleep 1.1
"unresponsive method returned"
end

def raise_specific_error_method
if @failure == true
raise SpecificException.new "SPECIFIC FAIL"
end

raise NotFoundException.new "NOT FOUND FAIL"
end

# Register this method with the circuit breaker...
#
circuit_method :call_external_method, :second_method, :unresponsive_method, :raise_specific_error_method

#
# Define what needs to be set for configuration...
#
circuit_handler do |handler|
handler.logger = Logger.new(STDOUT)
handler.failure_threshold = 5
handler.failure_timeout = 5
handler.invocation_timeout = 1
handler.excluded_exceptions = [NotFoundException]
end
@failure = false
end

def fail!
@failure = true
end

def succeed!
@failure = false
end

def call_external_method()
if @failure == true
raise "FAIL"
end

"hello world!"
end

def second_method()
raise 'EPIC FAIL'
end

def unresponsive_method
sleep 1.1
"unresponsive method returned"
end

def raise_specific_error_method
if @failure == true
raise SpecificException.new "SPECIFIC FAIL"
end

raise NotFoundException.new "NOT FOUND FAIL"
end

# Register this method with the circuit breaker...
#
circuit_method :call_external_method, :second_method, :unresponsive_method, :raise_specific_error_method

#
# Define what needs to be set for configuration...
#
circuit_handler do |handler|
handler.logger = Logger.new(STDOUT)
handler.failure_threshold = 5
handler.failure_timeout = 5
handler.invocation_timeout = 1
handler.excluded_exceptions = [NotFoundException]
end

end

Expand Down
88 changes: 88 additions & 0 deletions spec/circuit_state_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
require 'spec_helper'

describe CircuitBreaker::CircuitState do
subject { described_class.new }

describe "#initialize" do
it "sets the state to closed" do
expect(subject).to be_closed
end
end

shared_examples_for "trip" do
it "changes the state from :closed to :open" do
subject.trip
expect(subject).to be_open
end

it "changes the state from :half_open to :open" do
subject.trip
subject.attempt_reset
subject.trip
expect(subject).to be_open
end

if defined?(AASM::InvalidTransition)
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good enough

it "when AASM is defined, raises an AASM::InvalidTransition exception if invoked from :open" do
subject.trip
expect { subject.trip }.to raise_error(AASM::InvalidTransition)
end
else
it "raises a CircuitBreaker::InvalidTransition exception if invoked from :open" do
subject.trip
expect { subject.trip }.to raise_error(CircuitBreaker::InvalidTransition)
end
end
end

describe "#trip" do
include_examples "trip"
end

describe "#trip!" do
include_examples "trip"
end

describe "#attempt_reset" do
it "changes the state from :open to :half_open" do
subject.trip
subject.attempt_reset
expect(subject).to be_half_open
end

it "raises an exception if invoked from :half_open" do
subject.trip
subject.attempt_reset
expect { subject.attempt_reset }.to raise_error(CircuitBreaker::InvalidTransition)
end

it "raises an exception if invoked from :closed" do
expect { subject.attempt_reset }.to raise_error(CircuitBreaker::InvalidTransition)
end
end

describe "#reset" do
it "changes the state from :open to :closed" do
subject.trip
subject.reset
expect(subject).to be_closed
end

it "changes the state from :half_open to :closed" do
subject.trip
subject.attempt_reset
subject.reset
expect(subject).to be_closed
end

it "raises an exception if invoked from :closed" do
expect { subject.reset }.to raise_error(CircuitBreaker::InvalidTransition)
end

it "resets the failure count" do
subject.trip
subject.reset
expect(subject.failure_count).to eq(0)
end
end
end