splitclient-rb 3.0.3 → 3.1.0.pre.rc2

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
  SHA1:
3
- metadata.gz: bf91b9689764a6cd21126498db0c25132eaf8174
4
- data.tar.gz: c56a293e949a5f438ede87dc96496e6d5ae86599
3
+ metadata.gz: 848d0aacbc266ce731699e451bd82976446240e0
4
+ data.tar.gz: 3de885436b94b499290e29a0aac4ca1f1767e660
5
5
  SHA512:
6
- metadata.gz: e78db8256afcfcff7267a099389ddc1552799a54fb46901867972fa33be54a85c8bba4f65190878429f2e41fc58096fb1270076dd5fe52be13838d2171752978
7
- data.tar.gz: 138aa921d468371860f7dfd56907727278a17ffeca7d1ec50cfd1f5c4aaa9ee9717e9ce1394bac9eeb5f4c9dea18125ef2425012d349c6f6085c6e233f092640
6
+ metadata.gz: 550e6f8a4e3bf0b475946055fe432ce1a3790035170811a131270e25ee8dee6ebdfc70ce6baedd9797f07d56cea9cc1904fa23fe4dff630c3823463dd8fe3cf1
7
+ data.tar.gz: cc9e7d82c6878c698614de9b092a8ca705f884127213c9359bd1aafbeabd3abe20b08760d50e52cdba1d14b0013b65d76467993e3c3ab1ef21e20b34fe0bcb70
data/CHANGES.txt CHANGED
@@ -1,3 +1,6 @@
1
+ 3.1.0
2
+ - Add RedisAdapter
3
+
1
4
  3.0.3
2
5
  - Fix nil ref in manager
3
6
 
data/NEWS CHANGED
@@ -1,3 +1,6 @@
1
+ 3.1.0
2
+ Now supporting Redis as a cache adapter
3
+
1
4
  3.0.2
2
5
 
3
6
  now support also client.get_treatment( { :matching_key = 'bb' , "bucketing_key = ''}, ....)
data/README.md CHANGED
@@ -1,9 +1,9 @@
1
- # splitclient-rb
1
+ # Split Ruby SDK
2
2
 
3
- Ruby client for split software. This is provided as a gem that can be installed to your Ruby application
3
+ Ruby SDK for Split software, provided as a gem that can be installed to your Ruby application.
4
4
 
5
5
  ## Installation
6
- ----------
6
+ ---
7
7
 
8
8
  - Once the gem is published you can install it with the following steps:
9
9
 
@@ -15,30 +15,32 @@ Ruby client for split software. This is provided as a gem that can be installed
15
15
 
16
16
  And then execute:
17
17
 
18
- $ bundle
18
+ $ bundle install
19
19
 
20
20
  Or install it yourself as:
21
21
 
22
22
  $ gem install splitclient-rb
23
23
 
24
- - If the gem is still unpublished you can install it through this git repository with the following instructions:
24
+ - You can also use the most recent version from github:
25
25
 
26
- Add these lines to you application's Gemnfile
26
+ Add these lines to you application's `Gemfile`:
27
27
  ```ruby
28
- gem 'splitclient-rb', :git=>'https://github.com/splitio/ruby-client.git',
28
+ gem 'splitclient-rb', git: 'https://github.com/splitio/ruby-client.git',
29
29
  ```
30
- You can also specify any specific branch if necessary
30
+ You can also use any specific branch if necessary:
31
31
  ```ruby
32
- gem 'splitclient-rb', :git=>'https://github.com/splitio/ruby-client.git', :branch=>'development'
32
+ gem 'splitclient-rb', git: 'https://github.com/splitio/ruby-client.git', branch: 'development'
33
33
  ```
34
34
  And then execute:
35
35
 
36
36
  $ bundle install
37
37
 
38
38
  ## Usage
39
- ###Quick Setup
40
- ------
41
- Within your application you need the following
39
+
40
+ ### Quick Setup
41
+ ---
42
+
43
+ Within your application you need the following:
42
44
 
43
45
  Require the Split client:
44
46
  ```ruby
@@ -47,7 +49,7 @@ require 'splitclient-rb'
47
49
 
48
50
  Create a new split client instance with your API key:
49
51
  ```ruby
50
- factory = SplitIoClient::SplitFactory.new("your_api_key").client
52
+ factory = SplitIoClient::SplitFactory.new('YOUR_API_KEY').client
51
53
  split_client = factory.client
52
54
  ```
53
55
 
@@ -56,67 +58,101 @@ For advance use cases you can also obtain a `manager` instance from the factory.
56
58
  manager = factory.manager
57
59
  ```
58
60
 
59
- ###Ruby on Rails
60
- ----
61
- If you're using Ruby on Rails
61
+ ### Ruby on Rails
62
+ ---
62
63
 
63
- Create an initializer file at config/initializers/splitclient.rb and then initialize the split client :
64
+ Create an initializer: `config/initializers/splitclient.rb` and then initialize the split client:
64
65
  ```ruby
65
- Rails.configuration.split_client = SplitIoClient::SplitFactory.new("your_api_key").client
66
+ Rails.configuration.split_client = SplitIoClient::SplitFactory.new('YOUR_API_KEY').client
66
67
  ```
67
- In your controllers, access the client using
68
+ In your controllers, access the client using:
68
69
 
69
70
  ```ruby
70
71
  Rails.application.config.split_client
71
72
  ```
72
73
 
73
- ###Configuration
74
+ ### Configuration
74
75
  ---
75
- By default the split client uses its default configuration, it will be sufficient for most scenarios. However you can also provide custom configuration when initializing the client using an optional hash of options.
76
76
 
77
- The following values can be customized
77
+ Split client's default configuration should be sufficient for most scenarios. However you can also provide custom configuration when initializing the client using an optional hash of options.
78
+
79
+ The following values can be customized:
78
80
 
79
81
  **base_uri** : URI for the api endpoints
80
- *defualt value* : https://sdk.split.io/api/
81
82
 
82
- **local_store** : optional cache storage
83
- *default value* : custom cache local storage
83
+ *defualt value* = `https://sdk.split.io/api/`
84
84
 
85
85
  **connection_timeout** : timeout for network connections in seconds
86
- *default value* = 5
86
+
87
+ *default value* = `5`
87
88
 
88
89
  **read_timeout** : timeout for requests in seconds
89
- *default value* = 5
90
+
91
+ *default value* = `5`
90
92
 
91
93
  **features_refresh_rate** : The SDK polls Split servers for changes to feature roll-out plans. This parameter controls this polling period in seconds
92
- *default value* = 30
94
+
95
+ *default value* = `30`
93
96
 
94
97
  **segments_refresh_rate** : The SDK polls Split servers for changes to segment definitions. This parameter controls this polling period in seconds
95
- *default value* = 60
98
+
99
+ *default value* = `60`
96
100
 
97
101
  **metrics_refresh_rate** : The SDK sends diagnostic metrics to Split servers. This parameters controls this metric flush period in seconds
98
- *default value* = 60
102
+
103
+ *default value* = `60`
99
104
 
100
105
  **impressions_refresh_rate** : The SDK sends information on who got what treatment at what time back to Split servers to power analytics. This parameter controls how often this data is sent to Split servers in seconds
101
- *default value* = 60
106
+
107
+ *default value* = `60`
102
108
 
103
109
  **debug_enabled** : Enables extra logging
104
- *default value* = false
110
+
111
+ *default value* = `false`
105
112
 
106
113
  **transport_debug_enabled** : Enables extra transport logging
107
- *default value* = false
114
+
115
+ *default value* = `false`
108
116
 
109
117
  **logger** : default logger for messages and errors
110
- *default value* : Ruby logger class set to STDOUT
118
+
119
+ *default value* = `Logger.new($stdout)`
111
120
 
112
121
  **block_until_ready** : The SDK will block your app for provided amount of seconds until it's ready. If timeout expires `SplitIoClient::SDKBlockerTimeoutExpiredException` will be thrown. If `false` provided, then SDK would run in non-blocking mode
113
- *default value* : false
122
+
123
+ *default value* = `false`
124
+
125
+ **mode** : See [SDK modes section](#sdk-modes).
126
+
127
+ *default value* = `:standalone`
128
+
129
+ #### Cache adapter
130
+
131
+ The SDK needs some container to store fetched data, i.e. splits/segments. By default it will store everything in the application's memory, but you can also use Redis.
132
+
133
+ To use Redis, you have to include `redis-rb` in your app's Gemfile.
134
+
135
+ **cache_adapter** : Supported options: `:memory`, `:redis`
136
+
137
+ *default value* = `memory`
138
+
139
+ **redis_url** : Redis URL or hash with configuration for SDK to connect to.
140
+
141
+ *default value* = `'redis://127.0.0.1:6379/0'`
142
+
143
+ You can also use Sentinel like this:
144
+
145
+ ```ruby
146
+ SENTINELS = [{host: '127.0.0.1', port: 26380},
147
+ {host: '127.0.0.1', port: 26381}]
148
+
149
+ redis_url = Redis.new(url: 'redis://mymaster', sentinels: SENTINELS, role: :master)
150
+ ```
114
151
 
115
152
  Example
116
153
  ```ruby
117
154
  options = {
118
155
  base_uri: 'https://my.app.api/',
119
- local_store: Rails.cache,
120
156
  connection_timeout: 10,
121
157
  read_timeout: 5,
122
158
  features_refresh_rate: 120,
@@ -124,51 +160,62 @@ options = {
124
160
  metrics_refresh_rate: 360,
125
161
  impressions_refresh_rate: 360,
126
162
  logger: Logger.new('logfile.log'),
127
- block_until_ready: 5
163
+ block_until_ready: 5,
164
+ cache_adapter: :redis,
165
+ redis_url: 'redis://127.0.0.1:6379/0'
128
166
  }
129
167
  begin
130
- split_client = SplitIoClient::SplitFactory.new("your_api_key", options).client
168
+ split_client = SplitIoClient::SplitFactory.new('YOUR_API_KEY', options).client
131
169
  rescue SplitIoClient::SDKBlockerTimeoutExpiredException
132
170
  # Some arbitrary actions
133
171
  end
134
172
  ```
135
- This begin-rescue-end block is optional, you might want to use it to catch timeout expired exception and apply some logic here.
173
+ This begin-rescue-end block is optional, you might want to use it to catch timeout expired exception and apply some logic.
136
174
 
137
175
  ### Execution
138
176
  ---
139
- In your application code you just need to call the get_treatment method with the required parameters for key and feature name
177
+
178
+ In your application code you just need to call the `get_treatment` method with the required parameters for key and feature name:
140
179
  ```ruby
141
- split_client.get_treatment('user_id','feature_name', {attr: 'val'})
180
+ split_client.get_treatment('user_id','feature_name', attr: 'val')
142
181
  ```
143
182
 
144
183
  For example
145
184
  ```ruby
146
- if split_client.get_treatment('employee_user_01','view_main_list', {age: 35})
185
+ if split_client.get_treatment('employee_user_01','view_main_list', age: 35)
147
186
  my_app.display_main_list
148
187
  end
149
188
  ```
150
189
 
151
190
  Also, you can use different keys for actually getting treatment and sending impressions to the server:
152
191
  ```ruby
153
- split_client.get_treatment({ matching_key: 'user_id', bucketing_key: 'private_user_id' },'feature_name', {attr: 'val'})
192
+ split_client.get_treatment(
193
+ { matching_key: 'user_id', bucketing_key: 'private_user_id' },
194
+ 'feature_name',
195
+ attr: 'val'
196
+ )
154
197
  ```
155
198
  When it might be useful? Say, you have a user browsing your website and not signed up yet. You assign some internal id to that user (i.e. bucketing_key) and after user signs up you assign him a matching_key.
156
199
  By doing this you can provide both anonymous and signed up user with the same treatment.
157
200
 
158
201
  `bucketing_key` may be `nil` in that case `matching_key` would be used as a key, so calling
159
202
  ```ruby
160
- split_client.get_treatment({ matching_key: 'user_id' },'feature_name', {attr: 'val'})
203
+ split_client.get_treatment(
204
+ { matching_key: 'user_id' },
205
+ 'feature_name',
206
+ attr: 'val'
207
+ )
161
208
  ```
162
209
  Is exactly the same as calling
163
210
  ```ruby
164
- split_client.get_treatment('user_id' ,'feature_name', {attr: 'val'})
211
+ split_client.get_treatment('user_id' ,'feature_name', attr: 'val')
165
212
  ```
166
213
  `bucketing_key` must not be nil
167
214
 
168
215
  Also you can use the split manager:
169
216
 
170
217
  ```ruby
171
- split_manager = SplitIoClient::SplitFactory.new("your_api_key", options).manager
218
+ split_manager = SplitIoClient::SplitFactory.new('your_api_key', options).manager
172
219
  ```
173
220
 
174
221
  With the manager you can get a list of your splits by doing:
@@ -179,10 +226,81 @@ manager.splits
179
226
 
180
227
  And you should get something like this:
181
228
 
182
- ```bash
183
- => [{:name=>"some_feature", :traffic_type_name=>nil, :killed=>false, :treatments=>nil, :change_number=>1469134003507}, {:name=>"another_feature", :traffic_type_name=>nil, :killed=>false, :treatments=>nil, :change_number=>1469134003414}, {:name=>"even_more_features", :traffic_type_name=>nil, :killed=>false, :treatments=>nil, :change_number=>1469133991063}, {:name=>"yet_another_feature", :traffic_type_name=>nil, :killed=>false, :treatments=>nil, :change_number=>1469133757521}]
229
+ ```ruby
230
+ [
231
+ {
232
+ name: 'some_feature',
233
+ traffic_type_name: nil,
234
+ killed: false,
235
+ treatments: nil,
236
+ change_number: 1469134003507
237
+ },
238
+ {
239
+ name: 'another_feature',
240
+ traffic_type_name: nil,
241
+ killed: false,
242
+ treatments: nil,
243
+ change_number: 1469134003414
244
+ },
245
+ {
246
+ name: 'even_more_features',
247
+ traffic_type_name: nil,
248
+ killed: false,
249
+ treatments: nil,
250
+ change_number: 1469133991063
251
+ },
252
+ {
253
+ name: 'yet_another_feature',
254
+ traffic_type_name: nil,
255
+ killed: false,
256
+ treatments: nil,
257
+ change_number: 1469133757521
258
+ }
259
+ ]
184
260
  ```
185
261
 
262
+ ### SDK Modes
263
+
264
+ By default SDK would run alongside with your application and will be run in `standalone` mode, which includes two modes:
265
+ - `producer` - storing information from the Splits API in the chosen cache
266
+ - `consumer` - retrieving data from the cache and providing `get_treatment` interface
267
+
268
+ As you might think, you can choose between these 3 modes by providing `mode` option in the config.
269
+
270
+ #### Producer mode
271
+
272
+ If you have, say, one Redis cache which is used by several Split SDKs at once, e.g.: Python and Ruby, you want to have only one of them to write data to Redis, so it would remain consistent. That's why we have producer mode.
273
+
274
+ SDK can be ran in `producer` mode both in the scope of the application (e.g. as a part of the Rails app), and as a separate process. Let's see what steps are needed to run it as a separate process:
275
+
276
+ - You need to create a config file with .yml extension. All options specified in the above example section are valid, but you should write them in the YAML format, like this:
277
+
278
+ ```yaml
279
+ ---
280
+ :api_key: 'SECRET_API_KEY'
281
+ :base_uri: 'https://my.app.api/'
282
+ :connection_timeout: 10
283
+ :read_timeout: 5
284
+ :features_refresh_rate: 120
285
+ :segments_refresh_rate: 120
286
+ :metrics_refresh_rate: 360
287
+ :impressions_refresh_rate: 360
288
+ :block_until_ready: 5
289
+ :cache_adapter: :redis
290
+ :redis_url: 'redis://127.0.0.1:6379/0'
291
+ ```
292
+
293
+ - Install binstubs
294
+ ```ruby
295
+ bundle binstubs splitclient-rb
296
+ ```
297
+
298
+ - Run the executable provided by the SDK:
299
+ ```ruby
300
+ bundle exec bin/splitio -c ~/path/to/config/file.yml
301
+ ```
302
+
303
+ That's it!
186
304
 
187
305
  ## Development
188
306
 
data/exe/splitio ADDED
@@ -0,0 +1,37 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ lib = File.expand_path('../../lib', __FILE__)
4
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
5
+
6
+ require 'optparse'
7
+ require 'yaml'
8
+ require_relative '../lib/splitclient-rb'
9
+
10
+ ARGV << '-h' if ARGV.empty?
11
+
12
+ options = {}
13
+ opt_parser = OptionParser.new do |opts|
14
+ opts.banner = "Usage: splitio [options]"
15
+
16
+ opts.on("-cPATH", "--config=PATH", "Set the path to splitio.yml config file") do |c|
17
+ options[:config_path] = c
18
+ end
19
+
20
+ opts.on_tail("-h", "--help", "Prints this help") do
21
+ puts opts
22
+ exit
23
+ end
24
+ end
25
+
26
+ begin
27
+ opt_parser.parse!(ARGV)
28
+ rescue OptionParser::InvalidOption => e
29
+ puts e
30
+ puts opt_parser
31
+ exit(1)
32
+ end
33
+
34
+ config = YAML.load_file(options[:config_path])
35
+ config.merge!(mode: :producer, cache_adapter: :redis)
36
+
37
+ SplitIoClient::SplitFactory.new(config[:api_key], config)
@@ -3,42 +3,91 @@ require 'concurrent'
3
3
  module SplitIoClient
4
4
  module Cache
5
5
  module Adapters
6
- class MemoryAdapter < Adapter
6
+ class MemoryAdapter
7
7
  def initialize
8
8
  @map = Concurrent::Map.new
9
9
  end
10
10
 
11
11
  # Map
12
- def [](key)
12
+ def initialize_map(key)
13
+ @map[key] = Concurrent::Map.new
14
+ end
15
+
16
+ def add_to_map(key, field, value)
17
+ @map[key].put(field, value)
18
+ end
19
+
20
+ def find_in_map(key, field)
21
+ return nil if @map[key].nil?
22
+
23
+ @map[key].get(field)
24
+ end
25
+
26
+ def delete_from_map(key, fields)
27
+ if fields.is_a? Array
28
+ fields.each { |field| @map[key].delete(field) }
29
+ else
30
+ @map[key].delete(field)
31
+ end
32
+ end
33
+
34
+ def in_map?(key, field)
35
+ return false if @map[key].nil?
36
+
37
+ @map[key].key?(field)
38
+ end
39
+
40
+ def map_keys(key)
41
+ @map[key].keys
42
+ end
43
+
44
+ def get_map(key)
13
45
  @map[key]
14
46
  end
15
47
 
16
- def []=(key, obj)
17
- @map[key] = obj
48
+ # String
49
+ def string(key)
50
+ @map[key]
18
51
  end
19
52
 
20
- def initialize_map(key)
21
- @map[key] = Concurrent::Map.new
53
+ def set_string(key, str)
54
+ @map[key] = str
22
55
  end
23
56
 
24
- def add_to_map(key, map_key, map_value)
25
- @map[key].put(map_key, map_value)
57
+ def find_strings_by_prefix(prefix)
58
+ @map.keys.select { |str| str.start_with? prefix }
26
59
  end
27
60
 
28
- def find_in_map(key, map_key)
29
- return nil if @map[key].nil?
61
+ # Bool
62
+ def set_bool(key, val)
63
+ @map[key] = val
64
+ end
30
65
 
31
- @map[key].get(map_key)
66
+ def bool(key)
67
+ @map[key]
32
68
  end
33
69
 
34
- def delete_from_map(key, map_key)
35
- @map[key].delete(map_key)
70
+ # Set
71
+ alias_method :initialize_set, :initialize_map
72
+ alias_method :get_set, :map_keys
73
+ alias_method :delete_from_set, :delete_from_map
74
+ alias_method :in_set?, :in_map?
75
+
76
+ def add_to_set(key, values)
77
+ if values.is_a? Array
78
+ values.each { |value| add_to_map(key, value, 1) }
79
+ else
80
+ add_to_map(key, values, 1)
81
+ end
36
82
  end
37
83
 
38
- def in_map?(key, map_key)
39
- return false if @map[key].nil?
84
+ # General
85
+ def exists?(key)
86
+ !@map[key].nil?
87
+ end
40
88
 
41
- @map[key].key?(map_key)
89
+ def delete(key)
90
+ @map[key] = nil
42
91
  end
43
92
  end
44
93
  end
@@ -0,0 +1,95 @@
1
+ require 'redis'
2
+ require 'json'
3
+
4
+ module SplitIoClient
5
+ module Cache
6
+ module Adapters
7
+ class RedisAdapter
8
+ def initialize(redis_url)
9
+ connection = redis_url.is_a?(Hash) ? redis_url : { url: redis_url }
10
+
11
+ @redis = Redis.new(connection)
12
+ end
13
+
14
+ # Map
15
+ def initialize_map(key)
16
+ # No need to initialize hash/map in Redis
17
+ end
18
+
19
+ def add_to_map(key, field, value)
20
+ @redis.hset(key, field, value)
21
+ end
22
+
23
+ def find_in_map(key, field)
24
+ @redis.hget(key, field)
25
+ end
26
+
27
+ def delete_from_map(key, field)
28
+ @redis.hdel(key, field)
29
+ end
30
+
31
+ def in_map?(key, field)
32
+ @redis.hexists(key, field)
33
+ end
34
+
35
+ def map_keys(key)
36
+ @redis.hkeys(key)
37
+ end
38
+
39
+ def get_map(key)
40
+ @redis.hgetall(key)
41
+ end
42
+
43
+ # String
44
+ def string(key)
45
+ @redis.get(key)
46
+ end
47
+
48
+ def set_string(key, str)
49
+ @redis.set(key, str)
50
+ end
51
+
52
+ def find_strings_by_prefix(prefix)
53
+ @redis.keys("#{prefix}*")
54
+ end
55
+
56
+ # Bool
57
+ def set_bool(key, val)
58
+ @redis.set(key, val.to_s)
59
+ end
60
+
61
+ def bool(key)
62
+ @redis.get(key) == 'true'
63
+ end
64
+
65
+ # Set
66
+ alias_method :initialize_set, :initialize_map
67
+
68
+ def add_to_set(key, val)
69
+ @redis.sadd(key, val)
70
+ end
71
+
72
+ def delete_from_set(key, val)
73
+ @redis.srem(key, val)
74
+ end
75
+
76
+ def get_set(key)
77
+ @redis.smembers(key)
78
+ end
79
+
80
+ def in_set?(key, val)
81
+ @redis.sismember(key, val)
82
+ end
83
+
84
+ # General
85
+ def exists?(key)
86
+ @redis.exists(key)
87
+ end
88
+
89
+ def delete(key)
90
+ @redis.del(key)
91
+ end
92
+ end
93
+ end
94
+ end
95
+ end
@@ -1,24 +1,18 @@
1
1
  module SplitIoClient
2
2
  module Cache
3
3
  class Repository
4
- def initialize(adapter)
5
- @adapter = adapter
6
-
7
- @adapter[namespace_key('ready')] = false
8
- end
9
-
10
- def []=(key, obj)
11
- @adapter[namespace_key(key)] = obj
4
+ def set_string(key, str)
5
+ @adapter.set_string(namespace_key(key), str)
12
6
  end
13
7
 
14
- def [](key)
15
- @adapter[namespace_key(key)]
8
+ def string(key)
9
+ @adapter.string(namespace_key(key))
16
10
  end
17
11
 
18
12
  protected
19
13
 
20
14
  def namespace_key(key)
21
- "repository_#{key}"
15
+ "SPLITIO.#{key}"
22
16
  end
23
17
  end
24
18
  end
@@ -2,49 +2,59 @@ module SplitIoClient
2
2
  module Cache
3
3
  module Repositories
4
4
  class SegmentsRepository < Repository
5
+ KEYS_SLICE = 3000
6
+
7
+ def initialize(adapter)
8
+ @adapter = adapter
9
+
10
+ @adapter.set_bool(namespace_key('ready'), false)
11
+ end
12
+
5
13
  def add_to_segment(segment)
6
14
  name = segment[:name]
7
15
 
8
- @adapter.initialize_map(namespace_key("segments:#{name}")) if @adapter[namespace_key("segments:#{name}")].nil?
16
+ @adapter.initialize_set(segment_data(name)) unless @adapter.exists?(segment_data(name))
9
17
 
10
18
  add_keys(name, segment[:added])
11
19
  remove_keys(name, segment[:removed])
12
20
  end
13
21
 
14
22
  def get_segment_keys(name)
15
- @adapter[namespace_key("segments:#{name}")]
23
+ @adapter.get_set(segment_data(name))
16
24
  end
17
25
 
18
26
  def in_segment?(name, key)
19
- @adapter.in_map?(namespace_key("segments:#{name}"), key)
27
+ @adapter.in_set?(segment_data(name), key)
20
28
  end
21
29
 
22
30
  def used_segment_names
23
- @adapter['splits_repository_used_segment_names'].keys
31
+ @adapter.get_set(namespace_key('segments.registered'))
24
32
  end
25
33
 
26
34
  def set_change_number(name, last_change)
27
- @adapter.initialize_map(namespace_key('changes')) if @adapter[namespace_key('changes')].nil?
28
-
29
- @adapter.add_to_map(namespace_key('changes'), name, last_change)
35
+ @adapter.set_string(namespace_key("segment.#{name}.till"), last_change)
30
36
  end
31
37
 
32
38
  def get_change_number(name)
33
- @adapter.find_in_map(namespace_key('changes'), name) || -1
39
+ @adapter.string(namespace_key("segment.#{name}.till")) || -1
34
40
  end
35
41
 
36
42
  private
37
43
 
38
- def namespace_key(key)
39
- "segments_repository_#{key}"
44
+ def segment_data(name)
45
+ namespace_key("segmentData.#{name}")
40
46
  end
41
47
 
42
48
  def add_keys(name, keys)
43
- keys.each { |key| @adapter.add_to_map(namespace_key("segments:#{name}"), key, 1) }
49
+ keys.each_slice(KEYS_SLICE) do |keys_slice|
50
+ @adapter.add_to_set(segment_data(name), keys_slice)
51
+ end
44
52
  end
45
53
 
46
54
  def remove_keys(name, keys)
47
- keys.each { |key| @adapter.delete_from_map(namespace_key("segments:#{name}"), key) }
55
+ keys.each_slice(KEYS_SLICE) do |keys_slice|
56
+ @adapter.delete_from_set(segment_data(name), keys_slice)
57
+ end
48
58
  end
49
59
  end
50
60
  end
@@ -1,3 +1,5 @@
1
+ require 'concurrent'
2
+
1
3
  module SplitIoClient
2
4
  module Cache
3
5
  module Repositories
@@ -5,50 +7,57 @@ module SplitIoClient
5
7
  def initialize(adapter)
6
8
  @adapter = adapter
7
9
 
8
- @adapter[namespace_key('last_change')] = -1
9
- @adapter.initialize_map(namespace_key('splits'))
10
- @adapter.initialize_map(namespace_key('used_segment_names'))
10
+ @adapter.set_string(namespace_key('split.till'), '-1')
11
+ @adapter.initialize_map(namespace_key('segments.registered'))
11
12
  end
12
13
 
13
14
  def add_split(split)
14
- split_without_name = split.select { |k, _| k != :name }
15
-
16
- @adapter.add_to_map(namespace_key('splits'), split[:name], split_without_name)
15
+ @adapter.set_string(namespace_key("split.#{split[:name]}"), split.to_json)
17
16
  end
18
17
 
19
18
  def remove_split(name)
20
- @adapter.add_to_map(namespace_key('splits'), name, nil)
19
+ @adapter.delete(namespace_key("split.#{name}"))
21
20
  end
22
21
 
23
- def get_split(name)
24
- @adapter.find_in_map(namespace_key('splits'), name)
22
+ def get_split(name, prefixed = false)
23
+ split = prefixed ? @adapter.string(name) : @adapter.string(namespace_key("split.#{name}"))
24
+
25
+ JSON.parse(split, symbolize_names: true)
25
26
  end
26
27
 
27
- def list_splits()
28
- @adapter[namespace_key('splits')]
28
+ def splits
29
+ splits_hash = {}
30
+ splits = []
31
+ split_names = @adapter.find_strings_by_prefix(namespace_key('split'))
32
+
33
+ split_names.each do |name|
34
+ next if name == namespace_key('split.till')
35
+
36
+ splits << get_split(name, true)
37
+ end
38
+
39
+ splits.each do |split|
40
+ splits_hash[split[:name]] = split
41
+ end
42
+
43
+ splits_hash
29
44
  end
30
45
 
31
46
  def set_change_number(since)
32
- @adapter[namespace_key('last_change')] = since
47
+ @adapter.set_string(namespace_key('split.till'), since)
33
48
  end
34
49
 
35
50
  def get_change_number
36
- @adapter[namespace_key('last_change')]
51
+ @adapter.string(namespace_key('split.till'))
37
52
  end
38
53
 
39
54
  def set_segment_names(names)
40
55
  return if names.nil? || names.empty?
41
56
 
42
57
  names.each do |name|
43
- @adapter.add_to_map(namespace_key('used_segment_names'), name, 1)
58
+ @adapter.add_to_set(namespace_key('segments.registered'), name)
44
59
  end
45
60
  end
46
-
47
- private
48
-
49
- def namespace_key(key)
50
- "splits_repository_#{key}"
51
- end
52
61
  end
53
62
  end
54
63
  end
@@ -44,7 +44,7 @@ module SplitIoClient
44
44
  @sdk_blocker.splits_ready!
45
45
  @config.logger.info('splits are ready')
46
46
  end
47
-
47
+
48
48
  rescue StandardError => error
49
49
  @config.log_found_exception(__method__.to_s, error)
50
50
  end
@@ -40,7 +40,6 @@ module SplitIoClient
40
40
  #
41
41
  # @return [SplitIoClient] split.io client instance
42
42
  def initialize(api_key, config, splits_repository, segments_repository, sdk_blocker)
43
-
44
43
  @api_key = api_key
45
44
  @config = config
46
45
  @impressions = Impressions.new(100)
@@ -56,11 +55,25 @@ module SplitIoClient
56
55
  builder.adapter :net_http_persistent
57
56
  end
58
57
 
59
- @splits_consumer = create_splits_api_consumer
60
- @segments_consumer = create_segments_api_consumer
61
- @metrics_producer = create_metrics_api_producer
62
- @impressions_producer = create_impressions_api_producer
58
+ start_based_on_mode(@config.mode)
59
+ end
63
60
 
61
+ def start_based_on_mode(mode)
62
+ case mode
63
+ when :standalone
64
+ split_store
65
+ segment_store
66
+ metrics_sender
67
+ impressions_sender
68
+ when :consumer
69
+ metrics_sender
70
+ impressions_sender
71
+ when :producer
72
+ split_store
73
+ segment_store
74
+
75
+ sleep unless ENV['SPLITCLIENT_ENV'] == 'test'
76
+ end
64
77
  end
65
78
 
66
79
  #
@@ -69,11 +82,11 @@ module SplitIoClient
69
82
  # provided within the configuration
70
83
  #
71
84
  # @return [void]
72
- def create_splits_api_consumer
85
+ def split_store
73
86
  SplitIoClient::Cache::Stores::SplitStore.new(@splits_repository, @config, @api_key, @metrics, @sdk_blocker).call
74
87
  end
75
88
 
76
- def create_segments_api_consumer
89
+ def segment_store
77
90
  SplitIoClient::Cache::Stores::SegmentStore.new(@segments_repository, @config, @api_key, @metrics, @sdk_blocker).call
78
91
  end
79
92
 
@@ -116,11 +129,10 @@ module SplitIoClient
116
129
  # provided within the configuration
117
130
  #
118
131
 
119
- def create_metrics_api_producer
132
+ def metrics_sender
120
133
  Thread.new do
121
134
  loop do
122
135
  begin
123
- #post captured metrics
124
136
  post_metrics
125
137
 
126
138
  random_interval = randomize_interval @config.metrics_refresh_rate
@@ -132,11 +144,10 @@ module SplitIoClient
132
144
  end
133
145
  end
134
146
 
135
- def create_impressions_api_producer
147
+ def impressions_sender
136
148
  Thread.new do
137
149
  loop do
138
150
  begin
139
- #post captured impressions
140
151
  post_impressions
141
152
 
142
153
  random_interval = randomize_interval @config.impressions_refresh_rate
@@ -161,14 +172,14 @@ module SplitIoClient
161
172
  test_impression_array = []
162
173
  popped_impressions.each do |i|
163
174
  filtered = []
164
- keys_seen = []
175
+ keys_treatments_seen = []
165
176
 
166
177
  impressions = i[:impressions]
167
178
  impressions.each do |imp|
168
- if keys_seen.include?(imp.key)
179
+ if keys_treatments_seen.include?("#{imp.key}:#{imp.treatment}")
169
180
  next
170
181
  end
171
- keys_seen << imp.key
182
+ keys_treatments_seen << "#{imp.key}:#{imp.treatment}"
172
183
  filtered << imp
173
184
  end
174
185
 
@@ -5,8 +5,8 @@ require 'splitclient-rb/localhost_split_factory_builder'
5
5
  require 'splitclient-rb/localhost_split_factory'
6
6
  require 'splitclient-rb/split_config'
7
7
  require 'exceptions/sdk_blocker_timeout_expired_exception'
8
- require 'cache/adapters/adapter'
9
8
  require 'cache/adapters/memory_adapter'
9
+ require 'cache/adapters/redis_adapter'
10
10
  require 'cache/repositories/repository'
11
11
  require 'cache/repositories/segments_repository'
12
12
  require 'cache/repositories/splits_repository'
@@ -15,7 +15,6 @@ module SplitIoClient
15
15
  # @option opts [String] :events_uri ("https://events.split.io/api/") The events URL for events end points
16
16
  # @option opts [Int] :read_timeout (10) The read timeout for network connections in seconds.
17
17
  # @option opts [Int] :connection_timeout (2) The connect timeout for network connections in seconds.
18
- # @option opts [Object] :local_store A cache store for the Faraday HTTP caching library. Defaults to the Rails cache in a Rails environment, or a thread-safe in-memory store otherwise.
19
18
  # @option opts [Int] :features_refresh_rate The SDK polls Split servers for changes to feature roll-out plans. This parameter controls this polling period in seconds.
20
19
  # @option opts [Int] :segments_refresh_rate
21
20
  # @option opts [Int] :metrics_refresh_rate
@@ -27,7 +26,9 @@ module SplitIoClient
27
26
  def initialize(opts = {})
28
27
  @base_uri = (opts[:base_uri] || SplitConfig.default_base_uri).chomp('/')
29
28
  @events_uri = (opts[:events_uri] || SplitConfig.default_events_uri).chomp('/')
30
- @cache_adapter = opts[:cache_adapter] || SplitConfig.default_cache_adapter
29
+ @mode = opts[:mode] || SplitConfig.default_mode
30
+ @redis_url = opts[:redis_url] || SplitConfig.default_redis_url
31
+ @cache_adapter = SplitConfig.init_cache_adapter(opts[:cache_adapter] || SplitConfig.default_cache_adapter, @redis_url)
31
32
  @connection_timeout = opts[:connection_timeout] || SplitConfig.default_connection_timeout
32
33
  @read_timeout = opts[:read_timeout] || SplitConfig.default_read_timeout
33
34
  @features_refresh_rate = opts[:features_refresh_rate] || SplitConfig.default_features_refresh_rate
@@ -41,7 +42,7 @@ module SplitIoClient
41
42
  @machine_name = SplitConfig.get_hostname
42
43
  @machine_ip = SplitConfig.get_ip
43
44
 
44
- log_loaded_cache
45
+ startup_log
45
46
  end
46
47
 
47
48
  #
@@ -57,13 +58,11 @@ module SplitIoClient
57
58
  attr_reader :events_uri
58
59
 
59
60
  #
60
- # The store for the Faraday HTTP caching library. Stores should respond to
61
- # 'read', 'write' and 'delete' requests.
61
+ # The mode SDK will run
62
62
  #
63
- # @return [Object] The configured store for the Faraday HTTP caching library.
64
- attr_reader :local_store
63
+ # @return [Symbol] One of the available SDK modes: standalone, consumer, producer
64
+ attr_reader :mode
65
65
 
66
- #
67
66
  # The read timeout for network connections in seconds.
68
67
  #
69
68
  # @return [Int] The timeout in seconds.
@@ -114,6 +113,8 @@ module SplitIoClient
114
113
  attr_reader :metrics_refresh_rate
115
114
  attr_reader :impressions_refresh_rate
116
115
 
116
+ attr_reader :redis_url
117
+
117
118
  #
118
119
  # The default split client configuration
119
120
  #
@@ -134,9 +135,22 @@ module SplitIoClient
134
135
  'https://events.split.io/api/'
135
136
  end
136
137
 
138
+ def self.init_cache_adapter(adapter, redis_url)
139
+ case adapter
140
+ when :memory
141
+ SplitIoClient::Cache::Adapters::MemoryAdapter.new
142
+ when :redis
143
+ SplitIoClient::Cache::Adapters::RedisAdapter.new(redis_url)
144
+ end
145
+ end
146
+
147
+ def self.default_mode
148
+ :standalone
149
+ end
150
+
137
151
  # @return [LocalStore] configuration value for local cache store
138
152
  def self.default_cache_adapter
139
- SplitIoClient::Cache::Adapters::MemoryAdapter.new
153
+ :memory
140
154
  end
141
155
 
142
156
  #
@@ -187,6 +201,10 @@ module SplitIoClient
187
201
  false
188
202
  end
189
203
 
204
+ def self.default_redis_url
205
+ 'redis://127.0.0.1:6379/0'
206
+ end
207
+
190
208
  #
191
209
  # The default transport_debug_enabled value
192
210
  #
@@ -206,10 +224,11 @@ module SplitIoClient
206
224
  end
207
225
 
208
226
  #
209
- # log which cache class was loaded
227
+ # log which cache class was loaded and SDK mode
210
228
  #
211
229
  # @return [void]
212
- def log_loaded_cache
230
+ def startup_log
231
+ @logger.info("Loaded SDK in the #{@mode} mode")
213
232
  @logger.info("Loaded cache class: #{@cache_adapter.class}")
214
233
  end
215
234
 
@@ -49,19 +49,12 @@ module SplitIoClient
49
49
  #
50
50
  # @returns [object] array of splits
51
51
  def splits
52
-
53
52
  return @localhost_mode_features if @localhost_mode
54
- return nil if @splits_repository.nil?
55
-
56
- splits = @splits_repository.list_splits
57
- ret = []
58
- splits.keys.each do |key|
59
-
60
- split = splits.get(key)
61
- ret << build_split_view(key, split) if !Engine::Parser::SplitTreatment.archived?(split)
62
- end
53
+ return if @splits_repository.nil?
63
54
 
64
- ret
55
+ @splits_repository.splits.each_with_object([]) do |(name, split), memo|
56
+ memo << build_split_view(name, split) unless Engine::Parser::SplitTreatment.archived?(split)
57
+ end
65
58
  end
66
59
 
67
60
  #
@@ -75,8 +68,8 @@ module SplitIoClient
75
68
  end
76
69
 
77
70
  if @splits_repository
78
-
79
- split = @splits_repository.get_split(split_name)
71
+
72
+ split = @splits_repository.get_split(split_name)
80
73
 
81
74
  build_split_view(split_name, split) if split and !Engine::Parser::SplitTreatment.archived?(split)
82
75
  end
@@ -169,9 +162,9 @@ module SplitIoClient
169
162
  begin
170
163
  @adapter.impressions.log(matching_key, feature, result, (Time.now.to_f * 1000.0))
171
164
  latency = (Time.now - start) * 1000.0
172
- if (@adapter.impressions.queue.length >= @adapter.impressions.max_number_of_keys)
173
- @adapter.impressions_producer.wakeup
174
- end
165
+ # if (@adapter.impressions.queue.length >= @adapter.impressions.max_number_of_keys)
166
+ # @adapter.impressions_producer.wakeup
167
+ # end
175
168
  rescue StandardError => error
176
169
  @config.log_found_exception(__method__.to_s, error)
177
170
  end
@@ -1,3 +1,3 @@
1
1
  module SplitIoClient
2
- VERSION = '3.0.3'
2
+ VERSION = '3.1.0-rc2'
3
3
  end
@@ -35,4 +35,5 @@ Gem::Specification.new do |spec|
35
35
  spec.add_runtime_dependency "faraday-http-cache"
36
36
  spec.add_runtime_dependency "faraday_middleware"
37
37
  spec.add_runtime_dependency "net-http-persistent", "<= 2.9.4"
38
+ spec.add_runtime_dependency "redis", "~> 3.2"
38
39
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: splitclient-rb
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.0.3
4
+ version: 3.1.0.pre.rc2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Split Software
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2016-10-18 00:00:00.000000000 Z
11
+ date: 2016-10-28 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -220,10 +220,25 @@ dependencies:
220
220
  - - "<="
221
221
  - !ruby/object:Gem::Version
222
222
  version: 2.9.4
223
+ - !ruby/object:Gem::Dependency
224
+ name: redis
225
+ requirement: !ruby/object:Gem::Requirement
226
+ requirements:
227
+ - - "~>"
228
+ - !ruby/object:Gem::Version
229
+ version: '3.2'
230
+ type: :runtime
231
+ prerelease: false
232
+ version_requirements: !ruby/object:Gem::Requirement
233
+ requirements:
234
+ - - "~>"
235
+ - !ruby/object:Gem::Version
236
+ version: '3.2'
223
237
  description: Ruby client for using split SDK.
224
238
  email:
225
239
  - pato@split.io
226
- executables: []
240
+ executables:
241
+ - splitio
227
242
  extensions: []
228
243
  extra_rdoc_files: []
229
244
  files:
@@ -234,8 +249,9 @@ files:
234
249
  - NEWS
235
250
  - README.md
236
251
  - Rakefile
237
- - lib/cache/adapters/adapter.rb
252
+ - exe/splitio
238
253
  - lib/cache/adapters/memory_adapter.rb
254
+ - lib/cache/adapters/redis_adapter.rb
239
255
  - lib/cache/repositories/repository.rb
240
256
  - lib/cache/repositories/segments_repository.rb
241
257
  - lib/cache/repositories/splits_repository.rb
@@ -293,12 +309,12 @@ required_ruby_version: !ruby/object:Gem::Requirement
293
309
  version: '0'
294
310
  required_rubygems_version: !ruby/object:Gem::Requirement
295
311
  requirements:
296
- - - ">="
312
+ - - ">"
297
313
  - !ruby/object:Gem::Version
298
- version: '0'
314
+ version: 1.3.1
299
315
  requirements: []
300
316
  rubyforge_project:
301
- rubygems_version: 2.5.1
317
+ rubygems_version: 2.5.2
302
318
  signing_key:
303
319
  specification_version: 4
304
320
  summary: Ruby client for split SDK.
@@ -1,23 +0,0 @@
1
- module SplitIoClient
2
- module Cache
3
- module Adapters
4
- class Adapter
5
- def set
6
- raise NoMethodError
7
- end
8
-
9
- def get
10
- raise NoMethodError
11
- end
12
-
13
- def remove
14
- raise NoMethodError
15
- end
16
-
17
- def key?
18
- raise NoMethodError
19
- end
20
- end
21
- end
22
- end
23
- end