lib/faulty/storage/circuit_proxy.rb
# frozen_string_literal: true
class Faulty
module Storage
# A circuit wrapper for storage backends
#
# This class uses an internal {Circuit} to prevent the storage backend from
# causing application issues. If the backend fails continuously, this
# circuit will trip to prevent cascading failures. This internal circuit
# uses an independent in-memory backend by default.
class CircuitProxy
attr_reader :options
# Options for {CircuitProxy}
#
# @!attribute [r] circuit
# @return [Circuit] A replacement for the internal circuit. When
# modifying this, be careful to use only a reliable storage backend
# so that you don't introduce cascading failures.
# @!attribute [r] notifier
# @return [Events::Notifier] A Faulty notifier to use for circuit
# notifications. If `circuit` is given, this is ignored.
Options = Struct.new(
:circuit,
:notifier
) do
include ImmutableOptions
def finalize
raise ArgumentError, 'The circuit or notifier option must be given' unless notifier || circuit
self.circuit ||= Circuit.new(
Faulty::Storage::CircuitProxy.name,
notifier: Events::FilterNotifier.new(notifier, exclude: %i[circuit_success]),
cache: Cache::Null.new
)
end
end
# @param storage [Storage::Interface] The storage backend to wrap
# @param options [Hash] Attributes for {Options}
# @yield [Options] For setting options in a block
def initialize(storage, **options, &block)
@storage = storage
@options = Options.new(options, &block)
end
%i[
get_options
set_options
entry
open
reopen
close
lock
unlock
reset
status
history
list
clear
].each do |method|
define_method(method) do |*args|
options.circuit.run { @storage.public_send(method, *args) }
end
end
# This cache makes any storage fault tolerant, so this is always `true`
#
# @return [true]
def fault_tolerant?
@storage.fault_tolerant?
end
end
end
end