Rollout.io - Documentation

Flags Update Flow

Read this page to better understand when changes in Rollout's dashboard affects flag value on the end device (mobile, web, backend) and how these changes can be controlled by the developer.

Stateless Architecture

Rollout uses a Stateless Architecture (by default), which means that the SDK's PULL a static JSON file from Rollouts' cloud storage.

Rollout stateless architecture has a few key advantages:

  • Scale - Does not matter if you have 100 of users are millions of users, the SDK's fetches a static file.

  • Speed - no computation is done on Rollout servers, all flag computation is done in memory on the end platform

  • Resiliency - Rollout servers are NOT in the critical path of your software. If Rollout is down for some reason, aside from the not being able to make changes there is no effect to your application. Also, the SDK's have a caching mechanism, so in the case of a network issue, the effects are minimal as well.

  • Privacy - Rollout does not send or know any of your user private data, everything happens on the end application.

Note - Rollout also supports a Stateful Architecture, which means Rollout will hold an active connection with the end application and push a new JSON config file when config changes occur. If you are interested in this solution, please email us at support@rollout.io

Client Side SDK's Update Flow

SDK Setup Flow

The client SDK setup flow, operates in the following sequence:

  1. Synchronous local storage fetching:
    • The application calls the Rox SDK setup function
    • The SDK checks for existing configuration in the local storage
    • The configuration is applied synchronously
    • Setup function is returned with all configured experiments applied on the device
  2. Asynchronous network fetching
    • In Parallel to the local storage flow, an asynchronous network request is called from the SDK to Rollout Storage services
    • When the network request is returned:
    • Configuration is applied
    • Configuration is saved on local storage

SDK Foreground Configuration Fetching Flow

On relevant clients (mobile, TV, etc...) when the SDK identifies a foreground event (the application goes into foreground it triggers an asynchronous configuration fetching sequence to get a new configuration (if one exists) on Rollout Servers

Flag Freeze

Flag Freeze, Unfreeze and Freeze level is only available on client side SDKs

This feature is only available on Client side SDKs:

  • iOS (Swift, Objective-c, tvOS...)
  • Android
  • JavaScript (Browser, React Native, Tizen...)

The first time a flag is checked, the flag value will be frozen and kept for future evaluation. This will result in consistent behaviour for the user. Any change in remote configuration (fetched configuration) or change in custom properties values will not have an effect until the flag gets unfrozen

The flag gets unfrozen in the following scenario:

  • Next App launch (or page loading in a browser) - when the app is launched, when the flag value is checked by the app the value gets calculated based on the last fetched configuration
  • Next foreground event - (relevant only for mobile) when the app goes into the foreground the SDK calls unfreeze. This behaviour can be controlled by defining a different freeze levels
  • Unfreeze - when the unfreeze function gets called either on flag level or on

Flag Unfreeze

Flag Freeze, Unfreeze and Freeze level is only available on client side SDKs

The default behavior for mobile SDKs, is that the value will remain frozen until the next foreground event.
For Javascript SDK, the value will be frozen until the user closes the browser tab or window.
Since a property value might change while the app is running, when a flag depends on a property, we might want to unfreeze the value of the flag. For example, we might have a flag that is false for anonymous users but true for register users. If a user logs in after a flag value was already checked and frozen to false, we will want to tell the SDK to unfreeze it so that it will evaluate as true.
To unfreeze the flag state, we need to call the unfreeze function

Unfreeze all the flags:

Rox.unfreeze();
[Rox unfreeze];
Rox.unfreeze();
Rox.unfreeze();
Rox.unfreeze();

Unfreeze all flags under a specific namespace:

ROXCore.unfreezeNamespace(namespace);
[ROXCore unfreezeNamespace:namespace];
Rox.unfreeze(namespace);
Rox.unfreeze(namespace);
Rox.unfreeze(namespace);

Unfreeze a specific flag:

flag.unfreeze();
[flag unfreeze];
flag.unfreeze();
flag.unfreeze();
flag.unfreeze();

Flag Freeze Level

Flag Freeze, Unfreeze and Freeze level is only available on client side SDKs

Define different freeze level for a flag

  • none - no freeze at all, the flag value will be evaluated every time its value gets called.
  • untilForeground (default behavior) - The flag is consistent from foreground to background.
  • untilLaunch - until next launch time.

You can define flag level on an app level, defining the default flag level of the app using RoxOptions
options.defaultFreezeLevel or when creating the flag by supplying the flag level to the constructor

// setting default in options
let options = ROXOptions()
options.defaultFreezeLevel = .none
ROX.setup(withKey:appKey, options:options)
        
// overriding the default on flag constructor 
public let someFlag = RoxFlag(withDefault: false, freeze: .untilLaunch)!
// setting default in options
ROXOptions *options = [[ROXOptions alloc] init];
options.defaultFreezeLevel = ROXFreeze_none;
[ROX setupWithKey:@"a8c00ba1f04cf4ecbb396a2" options:options];
  
self.someFlag = [RoxFlag[init] initWithDefaultValue: YES freeze: ROXFreeze_untilLaunch];
// setting default in options
RoxOptions options = new RoxOptions.Builder().withFreeze(Freeze.None).build();
Rox.setup(this.getApplication(), options);

// overriding the default on flag constructor 
public RoxFlag someFlag = new RoxFlag(defaultValue, Freeze.UntilLaunch);
// setting default in options
Rox.setup(appKey, {
  freeze: 'none'
});
// overriding the default on flag constructor 
someFlag: new Rox.Flag(false, { freeze: 'untilLaunch' });
// setting default in options
Rox.setup(appKey, {
  freeze: 'none'
});
// overriding the default on flag constructor 
someFlag: new Rox.Flag(false, { freeze: 'untilLaunch' });

Server Side SDK's Update Flow

  • Rollout server-side SDK's fetch a new configuration file periodically, the default period is 30 seconds.

  • To change the default time frame, use FetchInterval / fetchIntervalInSec at the RoxOptions object, example:

RoxOptions options = new RoxOptions.Builder()
  .withFetchIntervalInSeconds(50)
  .withVersion("1.2.0")
  .build();
Rox.setup(this, options);
RoxOptions options = new RoxOptions(new RoxOptions.RoxOptionsBuilder{
	Version = "1.0.4",
	FetchInterval = 60
});
await Rox.Setup(appKey, options);
Rox.register('', container);
const options = {
  version: '2.0.0',
  fetchIntervalInSec: 60
};
Rox.setup('76aea5dd656a254d125c2a19',options);
from rox.server.rox import Rox
from rox.server.rox_options import RoxOptions

# setup configuration_fetched_handler in the options object
options = RoxOptions(
    version="1.3.1"
  	fetch_interval=60
)
cancel_event = Rox.setup('<key>', options).result();
// Coming soon

Configuration Fetched Handler

You can identify when Rollout SDK has loaded configuration from local storage or network by adding the onConfigurationFetched handler to RoxOptions.

Here is an example of using this handler on the different platforms

let options = ROXOptions()
options.onConfigurationFetched = { (result: RoxFetcherResult) -> Void in
   print(result)
};
ROX.setup(withKey:appKey, options:options)
RoxOptions options = new RoxOptions.Builder()
  .withConfigurationFetchedHandler(new ConfigurationFetchedHandler() {
            
    @Override
    public void onConfigurationFetched(FetcherResults results) {
      Logger.info("Got Rollout configuration");
    }
  }).build();

Rox.setup(this.getApplication(), options);
const configurationFetchedHandler = fetcherResults => {
  console.log(fetcherResults);
};

const options = {
  configurationFetchedHandler : configurationFetchedHandler
};

Rox.setup(appKey, options);
const configurationFetchedHandler = fetcherResults => {
  console.log(fetcherResults);
};

const options = {
  configurationFetchedHandler : configurationFetchedHandler
};

Rox.setup(appKey, options);
const configurationFetchedHandler = fetcherResults => {
  console.log(fetcherResults);
};

const options = {
  configurationFetchedHandler : configurationFetchedHandler
};

Rox.setup(appKey, options);
RoxOptions options = new RoxOptions.Builder()
  .withConfigurationFetchedHandler(new ConfigurationFetchedHandler() {
            
    @Override
    public void onConfigurationFetched(FetcherResults results) {
      Logger.info("Got Rollout configuration");
    }
  }).build();

Rox.setup(ROLLOUT_KEY, options);
var options = new RoxOptions(new RoxOptions.RoxOptionsBuilder
{
	ConfigurationFetchedHandler = (sender, e) =>
	{
		Console.WriteLine("Got Rollout configuration");
	}
});

Rox.setup(ROLLOUT_KEY, options);
from my_container import MyContainer
from rox.server.rox_server import Rox
from rox.server.rox_options import RoxOptions

my_container = MyContainer()
Rox.register('test', my_container)

# setup configuration_fetched_handler in the options object
options = RoxOptions(
    configuration_fetched_handler=lambda o:
        print("applied-from=%s creation-date=%s has-changes=%s error=%s" % (o.fetcher_status , o.creation_date , o.has_changes , o.error_details)  )
)

cancel_event = Rox.setup(ROLLOUT_KEY, options).result();
import (
	"github.com/rollout/rox-go/server"
	"github.com/rollout/rox-go/core/model"
)

type Container struct {}

var flags = &Container {}

rox.Register('test', flags)

// setup configurationFetchedHandler in the options object
options := server.NewRoxOptions(server.RoxOptionsBuilder {
	ConfigurationFetchedHandler: func(e *model.ConfigurationFetchedArgs) {
		fmt.Println("applied-from=", e.FetcherStatus ,"creation-date=", e.CreationDate,"has-changes=", e.HasChanges, "errors=", e.ErrorDetails)},
})

<- rox.Setup(ROLLOUT_KEY, options)
require_relative 'container'
require 'rox/server/rox_server'
require 'rox/server/rox_options'

configuration_fetched_handler = proc do |e|
  puts "applied-from=#{e.fetcher_status} creation-date=#{e.creation_date} has-changes=#{e.has_changes} error=#{e.error_details}"
end

container = Container()
Rox::Server::RoxServer.register('', container)

# setup configuration_fetched_handler in the options object
option = Rox::Server::RoxOptions.new(
  configuration_fetched_handler: configuration_fetched_handler
)

Rox::Server::RoxServer.setup(<ROLLOUT_KEY>, option).join

The FetcherResult has info regarding the actual fetch:

  • fetcherStatus - an enum that identifies which configuration was fetched (from the network, from local storage, an error occurred)
  • creationDate - Date of configuration creation
  • errorDetails - The description of the error if one exists
  • hasChanges - Does this configuration different from the one it is replacing

Invoking Fetch Programmatically

You can programmatically invoke fetch on demand from inside your app by using Rox.fetch() method

Verbosity level is a way to control the verbosity of the SDK. By default, the verbosity level is set to silent. Sometimes, in order to troubleshoot things, we will want to turn on the verbosity.
To turn verbosity using the SDK, we need to change the verbosity level to debug.