circuit_switch
circuit_switch is a gem for 'difficult' application; for example, few tests, too many meta-programming codes, low aggregation classes and few deploys.
This switch helps make changes easier and deploy safely.
You can deploy and check with a short code like following if the change is good or not, and when a problem is found, you can undo it without releasing it.
if CircuitSwitch.report.open?
# new codes
else
# old codes
end
You can also specify conditions to release testing features.
CircuitSwitch.run(if: -> { current_user.testing_new_feature? }) do
# testing feature codes
end
CircuitSwitch depends on ActiveRecord and ActiveJob.
Installation
Add this line to your application's Gemfile and run bundle install
:
gem 'circuit_switch'
Run generator to create initializer and modify config/initizlizers/circuit_switch.rb
:
rails generate circuit_switch:install
Generate a migration for ActiveRecord.
This table saves named key, circuit caller, called count, limit count and so on.
rails generate circuit_switch:migration circuit_switch
rails db:migrate
Usage
run
When you want to deploy and undo it if something unexpected happens, use CircuitSwitch.run
.
CircuitSwitch.run do
# experimental codes
end
run
basically calls the received proc. But when a condition is met, it closes the circuit and does not evaluate the proc.
To switch circuit opening and closing, a set of options can be set. Without options, the circuit is always open.
You can set close_if_reach_limit: true
so that the circuit is only open for 10 invocations. The constant 10 comes from the table definition we have arbitrarily chosen. In case you need a larger number, specify it in the limit_count
option in combination with close_if_reach_limit: true
, or alter default constraint on circuit_switches.run_limit_count
.
-
key
: [String] The identifier to find record by. Ifkey
has not been passed,circuit_switches.caller
is chosen as an alternative. -
if
: [Boolean, Proc] Calls proc when the value ofif
is evaluated truthy (default: true) -
close_if
: [Boolean, Proc] Calls proc when the value ofclose_if
is evaluated falsy (default: false) -
close_if_reach_limit
: [Boolean] Stops calling proc whencircuit_switches.run_count
has reachedcircuit_switches.run_limit_count
(default: false) -
limit_count
: [Integer] Mutatescircuit_switches.run_limit_count
whose value defined in schema is 10 by default. (default: nil)
Can't be set to 0 whenclose_if_reach_limit
is true. This option is only relevant whenclose_if_reach_limit
is set to true. -
initially_closed
: [Boolean] Creates switch with terminated mode (default: false)
To close the circuit at a specific date, code goes like:
CircuitSwitch.run(close_if: -> { Date.today >= some_day }) do
# testing codes
end
Or when the code of concern has been called 1000 times:
CircuitSwitch.run(close_if_reach_limit: true, limit_count: 1_000) do
# testing codes
end
To run other codes when circuit closes, run?
is available.
circuit_open = CircuitSwitch.run { ... }.run?
unless circuit_open
# other codes
end
CircuitSwitch.run.run?
has syntax sugar. open?
doesn't receive proc.
if CircuitSwitch.open?
# new codes
else
# old codes
end
report
When you just want to report, set your reporter
to initializer and then call CircuitSwitch.report
.
CircuitSwitch.report(if: some_condition)
report
just reports which line of code is called. It doesn't receive proc. It's useful for refactoring or removing dead codes.
Same as run
, a set of options can be set. By default, this method does not send reports more than 10 times. The constant 10 comes from the table definition we have arbitrarily chosen. In case you need a larger number, specify it in the limit_count
option, or alter default constraint on circuit_switches.report_limit_count
.
-
key
: [String] The identifier to find record by. Ifkey
has not been passed,circuit_switches.caller
is chosen as an alternative. -
if
: [Boolean, Proc] Reports when the value ofif
is evaluated truthy (default: true) -
stop_report_if
: [Boolean, Proc] Reports when the value ofstop_report_if
is evaluated falsy (default: false) -
stop_report_if_reach_limit
: [Boolean] Stops reporting whencircuit_switches.report_count
has reachedcircuit_switches.report_limit_count
(default: true) -
limit_count
: [Integer] Mutatescircuit_switches.report_limit_count
whose value defined in schema is 10 by default. (default: nil)
Can't be set to 0 whenstop_report_if_reach_limit
is true.
To know if report
has already been executed or not, you can get through reported?
.
Of course you can chain report
and run
or open?
.
with_backtrace
Reporter reports a short message by default like Watching process is called for 5th. Report until for 10th.
.
When your reporting tool knows about it's caller and backtrace, this is enough (e.g. Bugsnag).
When your reporting tool just reports plain message (e.g. Slack), you can set with_backtrace
true to initializer. Then report has a long message with backtrace like:
Watching process is called for 5th. Report until for 10th.
called_path: /app/services/greetings_service:21 block in validate
/app/services/greetings_service:20 validate
/app/services/greetings_service:5 call
/app/controllers/greetings_controller.rb:93 create
Test
To test, FactoryBot will look like this;
FactoryBot.define do
factory :circuit_switch, class: 'CircuitSwitch::CircuitSwitch' do
sequence(:key) { |n| "/path/to/file:#{n}" }
sequence(:caller) { |n| "/path/to/file:#{n}" }
due_date { Date.tomorrow }
trait :initially_closed do
run_is_terminated { true }
end
end
end
Task
When find a problem and you want to terminate running or reporting right now, execute a task with it's caller.
You can specify either key or caller.
rake circuit_switch:terminate_to_run[your_key]
rake circuit_switch:terminate_to_report[/app/services/greetings_service:21 block in validate]
In case of not Rails applications, add following to your Rakefile:
require 'circuit_switch/tasks'
Reminder for cleaning up codes
Too many circuits make codes messed up. We want to remove circuits after several days, but already have too many TODO things enough to remember them.
Let's forget it! Set due_date_notifier
to initializer and then call CircuitSwitch::DueDateNotifier
job daily. It will notify the list of currently registered switches.
By default, due_date is 10 days after today. To modify, set due_date
to initializer.
GUI to manage switches
GUI is now only for Rails.
Add the following to your config/routes.rb
and access /circuit_switch
.
Rails.application.routes.draw do
mount CircuitSwitch::Engine => 'circuit_switch'
end
Authentication
In production, you may need access protection.
With Devise, code goes like:
authenticate :user, lambda { |user| user.admin? } do
mount CircuitSwitch::Engine => 'circuit_switch'
end
Contributing
Bug reports and pull requests are welcome on GitHub at https://github.com/makicamel/circuit_switch. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the code of conduct.
License
The gem is available as open source under the terms of the MIT License.
Code of Conduct
Everyone interacting in the CircuitSwitch project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the code of conduct.