determinator 2.0.0 → 2.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c1b781c3d75121ccfad51eae6fa2faa71a6026d24570c3cf73daeb330e80f5e6
4
- data.tar.gz: 3adf8888c9f6f1e70b619afbd8b819be6981701274f502f5017688229a2f355f
3
+ metadata.gz: 2e45f2f0be23112e3ef9836aa58973333a2bb409ac3718d02dcc464f117c4911
4
+ data.tar.gz: 4489393bdd074715f545a1ce35419f0794fb47ed99a72af90f63d8885cf5c4fe
5
5
  SHA512:
6
- metadata.gz: f154018834b5fc5e7ff5f624272487a343c633ebcb5124b34c522694e8e0a0df606999243f0994d1e8d836eaec07fb3be8ef140d457542b948a8ddc4e2cea2ea
7
- data.tar.gz: c4d0bbbade1370255a2d33155d443ce8e440673daf0fc18c975eb268f1efc0ad56d0cf73fdf36aa4b24f708d1826f3c53de424e71e0b3505c9a5778f190c5b84
6
+ metadata.gz: e9c6833636ff9724dd8c6533d532aee97edac0303168bda759d8e2a693221bd4f9d10c55bde8adcd51d8859d35902892188f023a15482aad054a273b426ff169
7
+ data.tar.gz: ab4692c91ffcceb61018d08c165bf6c3bb7a6873103ffe91c6bf494d64f103a8f9a88e46462291024ea9476a00a9afdcfc32c863ade7807dc112857516e218e4
@@ -1,3 +1,8 @@
1
+ # v2.1.0 (2019-05-01)
2
+
3
+ Feature:
4
+ - Adds HttpRetriever and cascading cache support (#58)
5
+
1
6
  # v2.0.0 (2019-02-19)
2
7
 
3
8
  Breaking change:
data/README.md CHANGED
@@ -116,6 +116,46 @@ This configures the `Determinator.instance` with:
116
116
 
117
117
  You may also want to configure a `determinator` helper method inside your web request scope, see below for more information.
118
118
 
119
+ ### Using over http
120
+
121
+ Using the HttpRetriever will cause a request to be sent to actor tracking every time a feature is checked. The impact of this can be mitigated somewhat by having a short lived memory cache, but we're limited in
122
+ the length of time we can cache for without some way of notifying the cache that an item has changed.
123
+
124
+ ```ruby
125
+ faraday_connection = Faraday.new("http://actor-tracking.local") do |conn|
126
+ conn.headers['User-Agent'] = "Determinator - my service name"
127
+ conn.basic_auth('my-service-name', 'actor-tracking-token')
128
+ conn.adapter Faraday.default_adapter
129
+ end
130
+
131
+ Determinator.configure(
132
+ retrieval: Determinator::Retrieve::HttpRetriever.new(
133
+ connection: faraday_connection,
134
+ ),
135
+ feature_cache: Determinator::Cache::FetchWrapper.new(
136
+ ActiveSupport::Cache::MemoryStore.new(expires_in: 1.minute),
137
+ ActiveSupport::Cache::RedisCacheStore.new
138
+ )
139
+ )
140
+ ```
141
+
142
+ In this set up we've got two caches - some limited local cache and a larger redis cache that's shared between instances. The memory cache ensure that we're able to perform determination lookups in tight loops without excessive calls to redis.
143
+
144
+ We don't set a TTL on the redis cache (although we could) because we intend to expire the caches manually when we receive an update from our event bus:
145
+
146
+ ```ruby
147
+ feature_name = Determinator.retrieval.get_name("http://actor-tracking.local/features/some_feature")
148
+ Determinator.feature_cache.expire(feature_name)
149
+ ```
150
+
151
+ or in instances where the event bus provides a full feature object with a name it's simply:
152
+
153
+ ```ruby
154
+ Determinator.feature_cache.expire(deserialized_kafka_feature.name)
155
+ ```
156
+
157
+ This will expire both the limited local cache and the larger shared cache.
158
+
119
159
  ## Further Usage
120
160
 
121
161
  Once this is done you can ask for a determination like this:
@@ -154,12 +194,6 @@ determinator.which_variant(:my_experiment_name)
154
194
 
155
195
  Check the example Rails app in the `examples` directory for more information on how to make use of this gem.
156
196
 
157
- ### Routemaster
158
-
159
- Determinator's [Routemaster](https://github.com/deliveroo/routemaster) integration requires your application to be subscribed to the a `features` topic.
160
-
161
- The drain must expire the routemaster cache on receipt of events, making use of `Routemaster::Drain::CacheBusting.new` or similar is recommended.
162
-
163
197
  ### Using Determinator in RSpec
164
198
 
165
199
  * Include the `spec_helper.rb`.
@@ -7,6 +7,7 @@ require 'determinator/serializers/json'
7
7
 
8
8
  module Determinator
9
9
  class << self
10
+ attr_reader :feature_cache, :retrieval
10
11
  # @param :retrieval [Determinator::Retrieve::Routemaster] A retrieval instance for Features
11
12
  # @param :errors [#call, nil] a proc, accepting an error, which will be called with any errors which occur while determinating
12
13
  # @param :missing_feature [#call, nil] a proc, accepting a feature name, which will be called any time a feature is requested but isn't available
@@ -15,6 +16,7 @@ module Determinator
15
16
  self.on_error(&errors) if errors
16
17
  self.on_missing_feature(&missing_feature) if missing_feature
17
18
  @feature_cache = feature_cache if feature_cache.respond_to?(:call)
19
+ @retrieval = retrieval
18
20
  @instance = Control.new(retrieval: retrieval)
19
21
  end
20
22
 
@@ -89,5 +91,9 @@ module Determinator
89
91
 
90
92
  @feature_cache.call(name) { yield }
91
93
  end
94
+
95
+ def invalidate_cache(name)
96
+ @feature_cache.expire(name)
97
+ end
92
98
  end
93
99
  end
@@ -1,13 +1,30 @@
1
1
  module Determinator
2
2
  module Cache
3
3
  class FetchWrapper
4
- # @param cache [#fetch] An instance of a cache class which implements #fetch like ActiveSupport::Cache does
5
- def initialize(cache)
6
- @cache = cache
4
+ # @param *caches [ActiveSupport::Cache] If a list then the head of the the
5
+ # list should will be checked before the tail. If the head is empty but
6
+ # the tail is not then the head will be filled with the value of the tail.
7
+ def initialize(*caches)
8
+ @caches = caches
7
9
  end
8
10
 
11
+ # Call walks through each cache, returning a value if the item exists in
12
+ # any cache, otherwise popularing each cache with the value of yield.
9
13
  def call(feature_name)
10
- @cache.fetch(key(feature_name)) { yield }
14
+ value = read_and_upfill(feature_name)
15
+ # nil is an acceptable value in the case of a missing feature definition
16
+ return nil if value.nil?
17
+ return value if value != false
18
+
19
+ value_to_write = yield
20
+ @caches.each do |cache|
21
+ cache.write(key(feature_name), value_to_write)
22
+ end
23
+ return value_to_write
24
+ end
25
+
26
+ def expire(feature_name)
27
+ @caches.each{ |c| c.delete(key(feature_name)) }
11
28
  end
12
29
 
13
30
  private
@@ -15,6 +32,27 @@ module Determinator
15
32
  def key(feature_name)
16
33
  "determinator:feature_cache:#{feature_name}"
17
34
  end
35
+
36
+ # Walks through the list of caches, returning the first stored value.
37
+ #
38
+ # If a value is found in a cache after the first then all caches earlier
39
+ # in that list will be backfilled.
40
+ #
41
+ # @param url [String] a feature name
42
+ # @return [false, nil, Feature] false when no value is found, otherwise
43
+ # the value stored in the cache (including nil)
44
+ def read_and_upfill(feature_name)
45
+ @caches.each.with_index do |cache, index|
46
+ if cache.exist?(key(feature_name))
47
+ value = cache.read(key(feature_name))
48
+ @caches[0...index].each do |cache|
49
+ cache.write(key(feature_name), value)
50
+ end
51
+ return value
52
+ end
53
+ end
54
+ return false
55
+ end
18
56
  end
19
57
  end
20
58
  end
@@ -0,0 +1,35 @@
1
+ require 'faraday'
2
+
3
+ module Determinator
4
+ module Retrieve
5
+
6
+ class HttpRetriever
7
+ def initialize(connection:)
8
+ raise ArgumentError, "client must be a Faraday::Connection" unless connection.is_a?(Faraday::Connection)
9
+ @connection = connection
10
+ end
11
+
12
+ def retrieve(name)
13
+ begin
14
+ response = @connection.get("/features/#{name}")
15
+ return Determinator::Serializers::JSON.load(response.body) if response.status == 200
16
+ rescue => e
17
+ Determinator.notice_error(e)
18
+ end
19
+ nil
20
+ end
21
+
22
+ # Returns a feature name given a actor-tracking url. Used so we are able
23
+ # to expire a cache using a feature name given an event url.
24
+ #
25
+ # Not intended to be generic, and makes no guarantees about support for
26
+ # alternative url schemes.
27
+ #
28
+ # @param url [String] a actor tracking url
29
+ # @return [String, nil] a feature name or nil
30
+ def get_name(url)
31
+ (url.match('features\/(.*)\z') || [])[1]
32
+ end
33
+ end
34
+ end
35
+ end
@@ -1,3 +1,3 @@
1
1
  module Determinator
2
- VERSION = '2.0.0'
2
+ VERSION = '2.1.0'
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: determinator
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.0
4
+ version: 2.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - JP Hastings-Spital
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2019-02-19 00:00:00.000000000 Z
11
+ date: 2019-05-01 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: faraday
@@ -197,6 +197,7 @@ files:
197
197
  - lib/determinator/feature.rb
198
198
  - lib/determinator/retrieve/dynaconf.rb
199
199
  - lib/determinator/retrieve/file.rb
200
+ - lib/determinator/retrieve/http_retriever.rb
200
201
  - lib/determinator/retrieve/null_retriever.rb
201
202
  - lib/determinator/serializers/json.rb
202
203
  - lib/determinator/target_group.rb
@@ -222,7 +223,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
222
223
  version: '0'
223
224
  requirements: []
224
225
  rubyforge_project:
225
- rubygems_version: 2.7.8
226
+ rubygems_version: 2.7.6
226
227
  signing_key:
227
228
  specification_version: 4
228
229
  summary: Determine which experiments and features a specific actor should see.