determinator 2.0.0 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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.