Skip to content

Separate light and system settings#696

Open
bolshakov wants to merge 5 commits into
developfrom
feature/light-only-settings-2
Open

Separate light and system settings#696
bolshakov wants to merge 5 commits into
developfrom
feature/light-only-settings-2

Conversation

@bolshakov

@bolshakov bolshakov commented Jun 5, 2026

Copy link
Copy Markdown
Owner

Stoplight has two fundamentally different kinds of configuration that were previously mixed into the same parameter list:

Infrastructure configuration - how circuit breaker state is stored and reported:

  • data_store - where failure counts and state live (Memory, Redis)
  • notifiers — who gets told when a light changes color
  • error_notifier — who gets told when Stoplight itself has an internal error

Behavior configurationwhen a circuit breaks and recovers:

  • threshold, cool_off_time, window_size, recovery_threshold
  • tracked_errors, skipped_errors
  • traffic_control, traffic_recovery

These have different concerns. Infrastructure is a singleton operational decision - the whole application shares one Redis connection, one set of Slack notifiers. Behavior is per-circuit — the payment gateway trips at 5 errors in 60 seconds, the recommendation engine can tolerate more. Previously, Stoplight let you mix them freely, which caused a real class of bugs.

The footgun this eliminates:

Stoplight("Payment Service", data_store: Stoplight::DataStore::Memory.new).run { charge_card }

Every call to Stoplight() here creates a fresh Memory store. Since Memory stores state in a plain hash, each invocation starts with zero failures recorded. The circuit breaker never trips — it silently discards all failure history on every call. The behavior just doesn’t work.

This PR makes it a hard error. data_store:, notifiers:, and error_notifier: are no longer valid parameters to Stoplight(). You configure infrastructure once at boot:

# initializer
Stoplight.configure do |config|
  config.data_store = Stoplight::DataStore::Redis.new(redis)
  config.notifiers  = [Stoplight::Notifier::Logger.new(logger)]
end

# Anywhere in your app - behavior only
Stoplight("Payment Service", threshold: 5, window_size: 60).run { charge_card }

The right configuration lives in the right place, and the wrong thing is impossible to express.

Migration

The change is mechanical for the vast majority of users:

# Before
light = Stoplight("name", data_store: my_store, notifiers: [my_notifier], threshold: 5)

# After
Stoplight.configure do |config|
  config.data_store = my_store
  config.notifiers  = [my_notifier]
end
light = Stoplight("name", threshold: 5)

Escape Hatch: Systems

If you have genuinely separate infrastructure boundaries - say, one Redis for payment circuits and another for background jobs — Stoplight.system lets you own that explicitly:

payments_system = Stoplight.system("payments", data_store: Stoplight::DataStore::Redis.new(payments_redis))
payments_system.light("charge", threshold: 3).run { charge_card }

Most applications will never need this.

@bolshakov bolshakov marked this pull request as ready for review June 5, 2026 10:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant