Servers handle requests from multiple users as opposed to client applications that are built for a single user.
Sometimes the flag value might be related to the context of the call. The flag will need additional information in order to be evaluated correctly, which is available only at the time the flag is checked. For example, a flag value may only be determined once a user is authenticated.
This feature is only available for the following SDKs Server side SDKs:
Client side SDK:
|
Context object
Context is an object that is provided to a flag in order to evaluate the flag correctly for the specific call.
To create a context:
const userId = session.userId const user = db.readUserSync(userId); const shoppingCart = db.readShoppingCartSync(userId); const context = { user, shoppingCart }
let user = 'John' let email = '[email protected]' let context = { user, email }
const userId = session.userId const user = db.readUserSync(userId); const shoppingCart = db.readShoppingCartSync(userId); const context = { user, shoppingCart }
String userId = session.userId; User user = db.readUser(userId); ShoppingCart shoppingCart = db.readShoppingCart(userId); Map<String, Object> map = new HashMap<>(); map.put("user", user); map.put("shoppingCart", shoppingCart); Context context = new RoxContext.Builder().from(map);
var context = new ContextBuilder().Build( new Dictionary<string, object> { { "user", user } } );
context = {'user': user}
someContext := context.NewContext(map[string]interface{}{"user": user})
someContext = {'user' => user}
$context = (new ContextBuilder())->build([ "user" => user ]);
RoxContext *userContext = rox_context_create_from_map( ROX_MAP(ROX_COPY("userId"), rox_dynamic_value_create_int(555)));
Rox::Context *userContext = Rox::ContextBuilder() .AddIntValue("userId", 555) .Build();
Context keys are String’s
and values can be any type that is required in order to evaluate the flag correctly.
Providing context to a flag
When checking the flag value, the flag expects a context object that will be used for the evaluation.
const userId = session.userId const user = db.readUserSync(userId); const shoppingCart = db.readShoppingCartSync(userId); const context = { user, shoppingCart } const myContainer = { specialDiscount: new Rox.Flag() }; if (myContainer.specialDiscount.isEnabled(context)) { // enable special discount offer }
let user = 'John' let email = '[email protected]' let context = { user, email } let myContainer = { specialDiscount: new Rox.Flag() }; if (myContainer.specialDiscount.isEnabled(context)) { // enable special discount offer }
import {Flag} from 'rox-ssr'; const userId = session.userId const user = db.readUserSync(userId); const shoppingCart = db.readShoppingCartSync(userId); const context = { user, shoppingCart } const myContainer = { specialDiscount: new Flag() }; if (myContainer.specialDiscount.isEnabled(context)) { // enable special discount offer }
String userId = session.userId; User user = db.readUser(userId); ShoppingCart shoppingCart = db.readShoppingCart(userId); Map<String, Object> map = new HashMap<>(); map.put("user", user); map.put("shoppingCart", shoppingCart); Context context = new RoxContext.Builder().from(map); if (myContainer.specialDiscount.isEnabled(context)) { // enable special discount offer }
var context = new ContextBuilder().Build(new Dictionary<string, object> { { "user", user } }); if (container.specialDiscount.isEnabled(context)) { // this user is entitled to special discount }
context = {'user': user} if container.special_discount.is_enabled(context) : # this user is entitled to special discount
someContext := context.NewContext(map[string]interface{}{"user": user}) if (container.specialDiscount.isEnabled(someContext)) { // this user is entitled to special discount }
context = {'user': user} if container.spe.enabled?(context) #this user is entitled to special discount end
$context = (new ContextBuilder())->build([ "user" => user ]); if ($container->specialDiscount->isEnabled($context)) { // this user is entitled to special discount }
RoxVariant *specialDiscount = rox_add_flag("billing.isSpecialDiscount", false); RoxContext *userContext = rox_context_create_from_map( ROX_MAP(ROX_COPY("userId"), rox_dynamic_value_create_int(555))); bool isDiscounted = rox_flag_is_enabled_ctx(specialDiscount, userContext);
Rox::Flag *specialDiscount = Rox::Flag::Create("billing.isSpecialDiscount", false); Rox::Context *userContext = Rox::ContextBuilder() .AddIntValue("userId", 555) .Build(); bool isDiscounted = specialDiscount->IsEnabled(userContext);
Flags are configured using target groups. Now that the context has been defined, continue reading to learn how to use the context in properties.
Using context in custom properties
You can use context in custom properties. Target groups let you configure your flag based on properties. A custom property value can be different based on the context it receives.
Refer to Custom Properties for more information. |
You can define a custom property that is true for users with items in their shopping cart:
Rox.setCustomBooleanProperty("hasItemsInShoppingCart", context => !context.shoppingCart.isEmpty())
Rox.setCustomBooleanProperty("hasItemsInShoppingCart", context => !context.shoppingCart.isEmpty())
import {Rox} from 'rox-ssr'; Rox.setCustomBooleanProperty("hasItemsInShoppingCart", context => !context.shoppingCart.isEmpty())
Rox.setCustomComputedBooleanProperty("hasItemsInShoppingCart", new CustomPropertyGeneratorWithContext<Boolean>() { @Override public Boolean generateProperty(Context context) { return ! ((shoppingCart) context.get("shoppingCart")).isEmpty(); } });
Rox.SetCustomComputedBooleanProperty("hasItemsInShoppingCart", (context) => { return ! ((ShoppingCart) context.Get("shoppingCart")).isEmpty()})
Rox.set_custom_string_property('hasItemsInShoppingCart', lambda context: context['shopping_cart'].is_empty())
rox.SetCustomComputedBooleanProperty("hasItemsInShoppingCart", func(ctx context.Context) bool { value, _ := ctx.Get("shoppingCart").(*ShoppingCart) return value.IsEmpty() })
Rox::Server::RoxServer.set_custom_boolean_property('hasItemsInShoppingCart') do |context| context[:user].shopping_cart.empty? end
Rox::setCustomComputedBooleanProperty("hasItemsInShoppingCart", function (ContextInterface $context) { return (bool)($context->get("user")->getShoppingCart()->isEmpty()); });
RoxDynamicValue *check_for_empty_cart(void *target, RoxContext *context) { return rox_context_get(context, "itemsNumber") == 0; } rox_set_custom_computed_boolean_property("hasItemsInShoppingCart", NULL, &check_for_empty_cart);
class CheckForEmptyCart : public Rox::CustomPropertyGeneratorInterface { public: explicit CheckForEmptyCart() {} Rox::DynamicValue *operator()(Rox::Context *context) override { return rox_context_get(context, "itemsNumber") == 0; } }; CheckForEmptyCart checkForEmptyCart = CheckForEmptyCart(); Rox::SetCustomComputedProperty<bool>("hasItemsInShoppingCart", &checkForEmptyCart);
With this custom property, we can target all users who have items in their
shopping cart, and offer them a special discount. The context that is passed to the specialDiscount
flag is used to calculate the value of the hasItemsInShoppingCart
property specific for this user.
If there are one or more key-values which should be the same for all custom property calculations, you can use the global context (see below).
Global context
You can think of global context as a default context. If the context that is passed to a flag is missing a requirement for custom property calculation, the requirement will be available for the custom property as long as it exists in the global context. Creating a global context is exactly the same as for a regular context, except that you need to create it and set it when the SDK is initialized.
const user = createAnonymousUser(); const globalContext = { user }; Rox.setContext(globalContext);
let user = 'John'; let globalContext = { user }; Rox.setContext(globalContext);
import {Rox} from 'rox-ssr'; const user = createAnonymousUser(); const globalContext = { user }; Rox.setContext(globalContext);
Map<String, Object> map = new HashMap<>(); map.put("user", user); Context globalContext = new RoxContext.Builder().from(map); Rox.setGlobalContext(globalContext);
var globalContext = new ContextBuilder().Build(new Dictionary<string, object> { { "user", user } }); Rox.setContext(globalContext)
global_context = {'user': user} Rox.set_context(global_context)
globalContext := context.NewContext(map[string]interface{}{"user": user}) rox.SetContext(globalContext)
global_context = {'user': user} Rox::Server::RoxServer.context = global_context
$context = (new ContextBuilder())->build([ "user" => user ]); Rox::setContext($context);
RoxContext *userContext = rox_context_create_from_map( ROX_MAP(ROX_COPY("userId"), rox_dynamic_value_create_int(555))); rox_set_context(userContext)
Rox::Context *userContext = Rox::ContextBuilder() .AddIntValue("userId", 555) .Build(); Rox::SetContext(userContext);
The SDK ensures that the context that is passed to properties will include key-values from both the local context and the global context.
In a case where a key exists in both contexts, the key-value from the local context takes precedence.
Setting distinct_id from context
rox.distinct_id
is a default custom property that is used internally by the system for splitting values by percentage in the flag audience screen. On client-side SDKs, rox.distinct_id
is automatically generated and stored locally, in order to create a consistent user behavior when running a flag with split values.
On server-side SDKs, it is the developer’s responsibility to supply a consistent string value that can be used for percentage splitting.
You can choose userId as the value to be used for percentage splitting:
Rox.setCustomStringProperty("rox.distinct_id", context => { return context["user"].getId(); }); await Rox.setup(envKey);
import {Rox} from 'rox-ssr'; Rox.setCustomStringProperty("rox.distinct_id", context => { return context["user"].getId(); }); await Rox.setup(envKey);
Rox.setCustomComputedStringProperty("rox.distinct_id", context -> { return ((UserDetails)context.get("user")).getId(); }); Rox.setup(envKey).get()
Rox.setCustomComputedStringProperty("rox.distinct_id", context -> { return ((UserDetails)context.get("user")).getId(); }); Rox.setup(this);
Rox.SetCustomComputedStringProperty("rox.distinct_id", (context) => { return ((UserDetails) context.Get("user")).getId(); }); await Rox.setup(envKey);
# taking into account that context has user in it Rox.set_custom_string_property('rox.distinct_id', lambda context: context['user'].get_id()); Rox.setup(envKey).result();
rox.SetCustomComputedStringProperty("rox.distinct_id", func(ctx context.Context) string { return ctx.Get("user").(*User).id }) <-rox.setup(envKey, options)
# taking into account that context has user in it Rox::Server::RoxServer.set_custom_string_property('rox.distinct_id') do |context| context['user'].id end Rox::Server::RoxServer.setup(envKey).join
Rox::setCustomComputedStringProperty("rox.distinct_id", function (ContextInterface $context) { return $context->get("user")->getId(); }); Rox::setup(envKey);
RoxDynamicValue *get_user_id(void *target, RoxContext *context) { return rox_context_get(context, "id"); } rox_set_custom_computed_string_property("rox.distinct_id", NULL, &get_user_id);
class GetUserId : public Rox::CustomPropertyGeneratorInterface { public: explicit GetUserId() {} Rox::DynamicValue *operator()(Rox::Context *context) override { return rox_context_get(context, "id"); } }; GetUserId getUserId = GetUserId(); Rox::SetCustomComputedProperty<bool>("rox.distinct_id", &getUserId);