Optimizely Server Side
What is Optimizely Server Side ?
This is a wrapper on top of Optimizely's ruby sdk called optimizely-sdk . The sdk specializes in server side setup of A/B test . You can read more about it here .
If we have original sdk why need this wrapper ?
This gem solves few things:
- Syncing A/B test config across different servers when you don't want to fetch config via REST endpoint or redis/memcache store
Yes, it's designed keeping performance in mind as we want to save a network overhead and a extra dependency.
If you are using Optimizely you will be aware about the datafile. Once we make changes to the A/B test like change in percent distribution, start / pause a experiment this file get's updated.
If you have 50 servers with 40 passenger / puma process each these process needs to be updated. The Gem polls the config at regular interval and keeps the datafile cached across different process.
The config is stored in Memory Store . We use Activesupport memory store for same.
-
Helper methods to better handle test and variations and handling fallbacks and experiment pause
Optimizely ruby sdk provides us way to know which variation to show. But what happens when the experiment is paused ? Or there is a error happening in config.
More details about this in below section of experiment config.
Architecture
Getting Started
Add the gem in your Gemfile
gem 'optimizely_server_side'
and
bundle install
Add an initializer in config/initializers/optimizely_server_side.rb
#config/initializers/optimizely_server_side.rb
OptimizelyServerSide.configure do |config|
config.config_endpoint = 'https://cdn.optimizely.com/json/PROJECT_ID.json'
config.cache_expiry = 15 #(this is in minutes)
config.event_dispatcher = MyEventDispatcher.new
end
Config info
-
config_endpoint
- This is the Datafile endpoint which returns JSON config.PROJECT_ID
is a id of your server side project at https://app.optimizely.com . -
cache_expiry
- Time we want to keep the config cached in memory. -
event_dispatcher
- Optimizely needs to track every visit. You can pass your own event dispatcher from here. Read more -
user_attributes
- Everything related to user is passed from here. The must have key isvisitor_id
. In the same hash you can pass other other custom attributes. eq
config.user_attributes = {'visitor_id' => 1234, 'device_type' => 'iPhone'}
Optimizely needs a visitor_id
to track the unique user and server a constant experience.
In your Application controller
class ApplicationController < ActionController::Base
include OptimizelyServerSide::Support
before_action :set_visitor_id
def set_visitor_id
cookies.permanent[:visitor_id] = '1234567' #some visitor_id
# This links the browser cookie for visitor_id to
# OptimizelyServerSide
OptimizelyServerSide.configure do |config|
config.user_attributes = {'visitor_id' => cookies[:visitor_id]}
end
end
Example usage
In your html.erb
# in any app/view/foo.html.erb
<%= experiment(EXPERIMENT_KEY) do |exp|
exp.variation_one(VARIATION_ONE_KEY) do
render partial: 'variation_one_experience'
end
exp.variation_default(VARIATION_DEFAULT_KEY, primary: true)
render partial: 'variation_default_experience'
end
<% end %>
In your model or any PORO
class Foo
include OptimizelyServerSide::Support
# This method is responsible from getting data from
# any other rest endpoint.
# Suppose you are doing a AB test on a new endpoint / data source.
def get_me_some_data
data = experiment(EXPERIMENT_KEY) do |exp|
exp.variation_one(VARIATION_ONE_KEY) do
HTTParty.get('http://from_source_a.com/users')
end
exp.variation_default(VARIATION_TWO_KEY, primary: true) do
HTTParty.get('http://from_source_b.com/users')
end
end
end
end
Don't want to stick with variation_one, variation_two ?
You can call you own method names with variation_
. Below i have config.variation_best_experience
and config.variation_pathetic_experience
.
# in any app/view/foo.html.erb
<%= experiment(EXPERIMENT_KEY) do |exp|
config.variation_best_experience(VARIATION_ONE_KEY) do
render partial: 'variation_one_experience'
end
config.variation_pathetic_experience(VARIATION_DEFAULT_KEY, primary: true) do
render partial: 'variation_default_experience'
end
<% end %>
In the above examples:
EXPERIMENT_KEY
: When you will set your experiment this key will be set up that time at https://app.optimizely.com.
VARIATION_ONE_KEY
: Key for Variation one. This will be also set when setting up experiment.
VARIATION_TWO_KEY
: Key for Variation two. This will be also set when setting up experiment.
VARIATION_DEFAULT_KEY
: Key for default experience. This will be also set when setting up experiment
primary: true
: If you see above some variations are marked with primary: true
. This enables handling the fallback capabilities of optimizely_server_side. If there is any error pulling datafile or experiment is paused the primary
experience is served. Not setting primary won't give any experience during fallback times. We encourage setting it up.
Instrumentation
This is a trial feature and may or maynot exist in future version.
We have ActiveSupport::Notifications hooked up for few places which are worth monitoring.
- When the datafile is fetched from cdn. In your application you can subscribe via below. This helps to monitor the time it takes from CDN fetch
ActiveSupport::Notifications.subscribe "oss.call_optimizely_cdn" do |name, started, finished, unique_id, data|
Rails.logger.info "GET Datafile from Optimizely CDN in #{(finished - started) * 1000} ms"
end
- Which variation is being served currently. In your application you can subscribe via below
ActiveSupport::Notifications.subscribe "oss.variation" do |name, started, finished, unique_id, data|
Rails.logger.info "GET Variation from OSS in #{(finished - started) * 1000} ms with variation key #{data[:variation]}"
end
Testing
Gem uses rspec for unit testing
$~/D/p/w/optimizely_server_side> rspec .
......................................................
Finished in 0.12234 seconds (files took 0.5512 seconds to load)
54 examples, 0 failures
License
The MIT License