bolshakov / stoplight

:traffic_light: Traffic control for code.
http://bolshakov.github.io/stoplight/
MIT License
381 stars 40 forks source link

Stoplight 4.0 ๐ŸŽ‰ #196

Closed bolshakov closed 1 year ago

bolshakov commented 1 year ago

Stoplight 4.0 ๐ŸŽ‰

The next major release has been published, bringing a number of improvements and new features! However, it also introduces breaking changes. As a result, we removed certain notifiers. However, upgrading from Stoplight 3.x to Stoplight 4.0 need not be difficult. In fact, we have listed all the necessary steps in the UPGRADING.md documentation.

New Features

Stoplight() interface has changed

We aim to make Stoplight's configuration sharable across the code. The following change, enables Stoplight to run different code blocks with the same configuration:

light = Stoplight('http-api').with_cool_off_time(300)
light.run { call_this }
light.run { call_that }

The Stoplight() returns an immutable Stoplight::CircuitBreaker object that can be safely reconfigured. It makes it compatible with various Dependency Injections systems:

notifier = Stoplight::Sentry::Notifier.new(Sentry)
API_STOPLIGHT = Stoplight('http-api')
  .with_notifiers([notifier])
  .with_threshold(10)

class GetChangeOrdersApi
  def initialize(circuit_breaker: API_STOPLIGHT)
    @circuit_breaker = circuit_breaker.with_fallback { [] }
  end

  def call(order_id:)
    @circuit_breaker.run do 
      # calls HTTP API 
    end
  end
end

class GetOrderApi
  def initialize(circuit_breaker: API_STOPLIGHT)
    @circuit_breaker = circuit_breaker.with_fallback { NullOrder.new }
  end

  def call(order_id:)
    @circuit_breaker.run do
      # calls HTTP API 
    end
  end
end

Another benefit is that now you can easily inspect the status of the circuit breaker without passing an empty block:

light.color 

Introducing Sliding Window Error Counting

By default, every recorded failure contributes to reaching the threshold, regardless of when it occurs, causing the Stoplight to turn red. In this release, to provide more flexibility, we've introduced a sliding window configuration using the #with_window_size method (#172) that allows you to control how errors are counted within a specified time frame. Here's how it works:

Let's say you set the window size to 2 seconds:

window_size_in_seconds = 2

light = Stoplight('example-threshold')
  .with_window_size(window_size_in_seconds)
  .with_threshold(1)
# => #<Stoplight::CircuitBreaker:...>

light.run { 1 / 0 }#=> #<ZeroDivisionError: divided by 0>
sleep(3) 
light.run { 1 / 0 }

Without the window size configuration, the second light.run { 1 / 0 } call will result in a Stoplight::Error::RedLight exception being raised, as the Stoplight transitions to the red state after the first call. With a sliding window of 2 seconds, only the errors that occur within the latest 2 seconds are considered. The first error causes the Stoplight to turn red, but after 3 seconds (when the second error occurs), the window has shifted, and the Stoplight switches to green state causing the error to raise again. This provides a way to focus on the most recent errors.

It's worth noting that the default window size is set to infinity, meaning that all failures are counted regardless of timing.

Stoplight Gains an Interface for Locking Lights

In earlier versions, when you wanted to lock lights, you had to access Stoplight's internals. Stoplight 4.0 brings a new user-friendly interface to both lock and unlock lights:

light.lock('red')
light.unlock
light.lock('green')

What's Changed

Full Changelog: https://github.com/bolshakov/stoplight/compare/v3.0.1...v4.0.0