ankit8898/optimizely_config_provider

View on GitHub
Readme.md

Summary

Maintainability
Test Coverage
## Optimizely Server Side

[![Code Climate](https://codeclimate.com/github/ankit8898/optimizely_config_provider/badges/gpa.svg)](https://codeclimate.com/github/ankit8898/optimizely_config_provider) [![Build Status](https://travis-ci.org/ankit8898/optimizely_server_side.svg?branch=master)](https://travis-ci.org/ankit8898/optimizely_server_side)
[![Gem Version](https://badge.fury.io/rb/optimizely_server_side.svg)](https://badge.fury.io/rb/optimizely_server_side)
[![Test Coverage](https://codeclimate.com/github/ankit8898/optimizely_config_provider/badges/coverage.svg)](https://codeclimate.com/github/ankit8898/optimizely_config_provider/coverage)

### What is Optimizely Server Side ?

This is a wrapper on top of [Optimizely's](https://app.optimizely.com/projects) ruby sdk called [optimizely-sdk](https://github.com/optimizely/ruby-sdk) . The sdk specializes in server side setup of A/B test . You can read more about it [here](http://developers.optimizely.com/server/introduction/index.html) .

### 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](http://developers.optimizely.com/server/reference/index.html#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](http://api.rubyonrails.org/classes/ActiveSupport/Cache/MemoryStore.html) 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

![alt Architecture](https://github.com/ankit8898/optimizely_server_side/blob/master/docs/general_architecture.png
 "Architecture")

### Getting Started

Add the gem in your Gemfile

```ruby
 gem 'optimizely_server_side'
```

and

```ruby
bundle install
```

Add an initializer in `config/initializers/optimizely_server_side.rb`

```ruby
#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](https://developers.optimizely.com/server/reference/index#event-dispatcher)
- `user_attributes` - Everything related to user is passed from here. The must have key is `visitor_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

```ruby
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

```ruby
# 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

```ruby
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`.


```ruby
# 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.

![alt Optimizely dashboard](https://github.com/ankit8898/optimizely_server_side/blob/master/docs/screenshot.png "Optimizely dashboard")


### Instrumentation

This is a trial feature and may or maynot exist in future version.

We have [ActiveSupport::Notifications](http://api.rubyonrails.org/classes/ActiveSupport/Notifications.html) 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

```ruby
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

```ruby

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

```ruby
$~/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