Skip to content

Commit

Permalink
Remove instrumentation from adapter and use isntrumented decorator.
Browse files Browse the repository at this point in the history
  • Loading branch information
jnunemaker committed Feb 15, 2013
1 parent 96978a2 commit ad67e79
Show file tree
Hide file tree
Showing 3 changed files with 16 additions and 206 deletions.
93 changes: 12 additions & 81 deletions lib/flipper/adapter.rb
Expand Up @@ -2,8 +2,7 @@

module Flipper
# Internal: Adapter wrapper that wraps vanilla adapter instances. Adds things
# like local caching and convenience methods for adding/reading features from
# the adapter.
# like local caching and instrumentation.
#
# So what is this local cache crap?
#
Expand All @@ -21,43 +20,13 @@ module Flipper
# To see an example adapter that this would wrap, checkout the [memory
# adapter included with flipper](https://github.com/jnunemaker/flipper/blob/master/lib/flipper/adapters/memory.rb).
class Adapter
# Private: The name of instrumentation events.
InstrumentationName = "adapter_operation.#{InstrumentationNamespace}"

# Internal: Wraps vanilla adapter instance for use internally in flipper.
#
# object - Either an instance of Flipper::Adapter or a vanilla adapter instance
#
# Examples
#
# adapter = Flipper::Adapters::Memory.new
# instance = Flipper::Adapter.new(adapter)
#
# Flipper::Adapter.wrap(instance)
# # => Flipper::Adapter instance
#
# Flipper::Adapter.wrap(adapter)
# # => Flipper::Adapter instance
#
# Returns Flipper::Adapter instance
def self.wrap(object, options = {})
if object.is_a?(Flipper::Adapter)
object
else
new(object, options)
end
end

# Private: What adapter is being wrapped and will ultimately be used.
attr_reader :adapter

# Private: The name of the adapter. Based on the class name.
attr_reader :name

# Private: What is used to store the local cache.
# Private: What is used to store the operation cache.
attr_reader :local_cache

# Private: What is used to instrument all the things.
# Private: What is instrumenting all the operations.
attr_reader :instrumenter

# Internal: Initializes a new adapter instance.
Expand All @@ -69,12 +38,12 @@ def self.wrap(object, options = {})
# :local_cache - Where to store the local cache data (default: {}).
# Must respond to fetch(key, block), delete(key)
# and clear.
# :instrumenter - What to use to instrument all the things.
#
def initialize(adapter, options = {})
@adapter = adapter
@local_cache = options[:local_cache] || {}
@instrumenter = options.fetch(:instrumenter, Flipper::Instrumenters::Noop)
@adapter = Adapters::Instrumented.new(adapter, {
:instrumenter => @instrumenter,
})
end

# Public: Turns local caching on/off.
Expand All @@ -97,31 +66,18 @@ def name

# Public: Reads all keys for a given feature.
def get(feature)
payload = {
:operation => :get,
:adapter_name => name,
:feature_name => feature.name,
}

if using_local_cache?
local_cache.fetch(feature.name) {
local_cache[feature.name] = instrument_operation(:get, payload, feature)
local_cache[feature.name] = @adapter.get(feature)
}
else
instrument_operation(:get, payload, feature)
@adapter.get(feature)
end
end

# Public: Enable feature gate for thing.
def enable(feature, gate, thing)
payload = {
:operation => :enable,
:adapter_name => name,
:feature_name => feature.name,
:gate_name => gate.name,
}

result = instrument_operation(:enable, payload, feature, gate, thing)
result = @adapter.enable(feature, gate, thing)

if using_local_cache?
local_cache.delete(feature.name)
Expand All @@ -132,14 +88,7 @@ def enable(feature, gate, thing)

# Public: Disable feature gate for thing.
def disable(feature, gate, thing)
payload = {
:operation => :disable,
:adapter_name => name,
:feature_name => feature.name,
:gate_name => gate.name,
}

result = instrument_operation(:disable, payload, feature, gate, thing)
result = @adapter.disable(feature, gate, thing)

if using_local_cache?
local_cache.delete(feature.name)
Expand All @@ -150,30 +99,12 @@ def disable(feature, gate, thing)

# Public: Returns all the features that the adapter knows of.
def features
payload = {
:operation => :features,
:adapter_name => name,
}

instrument_operation :features, payload
@adapter.features
end

# Internal: Adds a known feature to the set of features.
def add(feature)
payload = {
:operation => :add,
:adapter_name => name,
:feature_name => feature.name,
}

instrument_operation :add, payload, feature
end

# Private: Instruments operation with payload.
def instrument_operation(operation, payload = {}, *args)
@instrumenter.instrument(InstrumentationName, payload) { |payload|
payload[:result] = @adapter.send(operation, *args)
}
@adapter.add(feature)
end
end
end
2 changes: 1 addition & 1 deletion lib/flipper/dsl.rb
Expand Up @@ -16,7 +16,7 @@ class DSL
# :instrumenter - What should be used to instrument all the things.
def initialize(adapter, options = {})
@instrumenter = options.fetch(:instrumenter, Flipper::Instrumenters::Noop)
@adapter = Adapter.wrap(adapter, :instrumenter => @instrumenter)
@adapter = Flipper::Adapter.new(adapter, :instrumenter => @instrumenter)
@memoized_features = {}
end

Expand Down
127 changes: 3 additions & 124 deletions spec/flipper/adapter_spec.rb
Expand Up @@ -11,60 +11,10 @@

subject { described_class.new(adapter, :local_cache => local_cache) }

describe ".wrap" do
context "with Flipper::Adapter instance" do
before do
@result = described_class.wrap(subject)
end

it "returns same Flipper::Adapter instance" do
@result.should equal(subject)
end

it "wraps adapter that instance was wrapping" do
@result.adapter.should be(subject.adapter)
end
end

context "with adapter instance" do
before do
@result = described_class.wrap(adapter)
end

it "returns Flipper::Adapter instance" do
@result.should be_instance_of(described_class)
end

it "wraps adapter" do
@result.adapter.should be(adapter)
end
end

context "with adapter instance and options" do
let(:instrumenter) { double('Instrumentor') }

before do
@result = described_class.wrap(adapter, :instrumenter => instrumenter)
end

it "returns Flipper::Adapter instance" do
@result.should be_instance_of(described_class)
end

it "wraps adapter" do
@result.adapter.should be(adapter)
end

it "passes options to initialization" do
@result.instrumenter.should be(instrumenter)
end
end
end

describe "#initialize" do
it "sets adapter" do
it "wraps adapter with instrumentation" do
instance = described_class.new(adapter)
instance.adapter.should be(adapter)
instance.adapter.should be_instance_of(Flipper::Adapters::Instrumented)
end

it "sets adapter name" do
Expand All @@ -81,6 +31,7 @@
instrumenter = double('Instrumentor', :instrument => nil)
instance = described_class.new(adapter, :instrumenter => instrumenter)
instance.instrumenter.should be(instrumenter)
instance.adapter.instrumenter.should be(instrumenter)
end
end

Expand Down Expand Up @@ -180,76 +131,4 @@
end
end
end

describe "instrumentation" do
let(:instrumenter) { Flipper::Instrumenters::Memory.new }
let(:feature) { flipper[:stats] }
let(:gate) { feature.gate(:percentage_of_actors) }
let(:thing) { flipper.actors(22) }

subject {
described_class.new(adapter, :instrumenter => instrumenter)
}

it "is recorded for get" do
result = subject.get(feature)

event = instrumenter.events.last
event.should_not be_nil
event.name.should eq('adapter_operation.flipper')
event.payload[:operation].should eq(:get)
event.payload[:adapter_name].should eq(:memory)
event.payload[:feature_name].should eq(:stats)
event.payload[:result].should be(result)
end

it "is recorded for enable" do
result = subject.enable(feature, gate, thing)

event = instrumenter.events.last
event.should_not be_nil
event.name.should eq('adapter_operation.flipper')
event.payload[:operation].should eq(:enable)
event.payload[:adapter_name].should eq(:memory)
event.payload[:feature_name].should eq(:stats)
event.payload[:gate_name].should eq(:percentage_of_actors)
event.payload[:result].should be(result)
end

it "is recorded for disable" do
result = subject.disable(feature, gate, thing)

event = instrumenter.events.last
event.should_not be_nil
event.name.should eq('adapter_operation.flipper')
event.payload[:operation].should eq(:disable)
event.payload[:adapter_name].should eq(:memory)
event.payload[:feature_name].should eq(:stats)
event.payload[:gate_name].should eq(:percentage_of_actors)
event.payload[:result].should be(result)
end

it "is recorded for add" do
result = subject.add(feature)

event = instrumenter.events.last
event.should_not be_nil
event.name.should eq('adapter_operation.flipper')
event.payload[:operation].should eq(:add)
event.payload[:adapter_name].should eq(:memory)
event.payload[:feature_name].should eq(:stats)
event.payload[:result].should be(result)
end

it "is recorded for features" do
result = subject.features

event = instrumenter.events.last
event.should_not be_nil
event.name.should eq('adapter_operation.flipper')
event.payload[:operation].should eq(:features)
event.payload[:adapter_name].should eq(:memory)
event.payload[:result].should be(result)
end
end
end

0 comments on commit ad67e79

Please sign in to comment.