In more complex codebases, a single customer interaction will often involve multiple services and languages. For example, maybe you want to:
- Run an experiment spanning multiple services written in different languages–for example, when using a microservice architecture, or when migrating from an old stack to a new one.
Optimizely provides Full Stack SDKs for most major languages. This guide describes some common use cases for multiple languages, demonstrates how to get started with multiple languages, and addresses some common implementation questions.
If you plan to use multiple SDKs, we strongly recommend using SDK versions released after April 2018 (version 2.0 or later for most of the Full Stack SDKs). These versions generate a v4 datafile with full Feature Management support that works across all version 2.0 SDKs.
For help with choosing the correct SDK version or versions for your particular implementation, contact Optimizely Support.
If you have existing projects using pre-v2.0 SDK versions released before April 2018, you can still use them across multiple languages. However, there are some important restrictions:
You cannot experiment across Full Stack and Mobile projects. Pre-2.0 Full Stack and Mobile projects generate incompatible datafiles (v2 and v3, respectively).
You cannot use Feature Management with existing Mobile projects. To use Feature Management with Mobile, install a 2.x or 3.x SDK and determine if you need to create a new Full Stack project.
All Optimizely SDKs use MurmurHash3 to determine whether a user is bucketed into a experiment, and if so, which variation they receive. This is based on the user ID and the experiment key. The bucketing code is shared across all Optimizely SDKs.
This means bucketing is deterministic you will always get the same result when bucketing a given user ID into a given experiment) and consistent across SDKs (given a common datafile, you will get the same result bucketing user ID ‘abc’ into experiment ‘123’ with the Python SDK as with any other SDK).
This behavior is the core of Full Stack’s support for multiple languages. However, it relies on three assumptions:
You must use the same datafile across SDKs. Learn more about best practices for Manage Config (datafile).
You must use the same user ID and attributes when bucketing a user across SDKs. If all your SDKs use the same deterministic method to generate user IDs, then you’re all set. However, if you use random user IDs, you’ll need to pass these from the server to any client SDKs. See Sharing user IDs and attributes with client SDKs below.
If you use a User Profile Service with one SDK, you must implement it consistently across all SDKs. For example, if a user profile service is implemented on your iOS client (this is the default behavior) but not on your Ruby backend, and you changed traffic allocation for a running experiment, some users would be bucketed inconsistently across client and server. To avoid this, you would have to implement the same user profile service in your Ruby backend as well.
Because of the deterministic and consistent nature of bucketing, it is fine to bucket users multiple times across SDKs. However, when experimenting across server-side and client-side SDKs, you might find it helpful to bucket users on the server-side to ensure a consistent experience:
- If your client does not have access to the user ID and/or attributes needed for bucketing, you can bucket users on the server, then pass along the decision and needed data to the client
- If you maintain many different client SDKs across different platforms, it may lighten your implementation workload to consolidate bucketing
When using both client and server SDKs, it may be necessary to pass the user ID from the server to the client to ensure proper conversion event tracking. You may also want to pass along user attributes only available on the server for use in tracking conversion events.
Most customers find that as long as each SDK implementation has a relatively short cache expiration, occasional brief discrepancies between datafiles are okay. However, if you need to ensure strict consistency, you can synchronize the datafile between the client and server. This allows both to instantiate an Optimizely client from exactly the same datafile. This eliminate any risk of inconsistencies that could be introduced by the client and server each fetching the datafile separately from the Optimizely CDN at different times (for example, if the CDN were in the process of propagating updates from the Optimizely app just as the client and server separately fetched the datafile).
In this approach:
- The server instantiates using a datafile fetched from the Optimizely CDN.
- The server retrieves a JSON representation of its datafile, using the
var datafile = optimizelyClientInstance.getOptimizelyConfig().getDatafile();
- The server passes the datafile to the client, for example as part of a rendered HTML page.
- The client instantiates from the passed datafile.
|- Mitigates flickering scenarios: If the datafile contents live inline in the page source, the client instantiation doesn’t have to wait until the XHR fetch is complete.|
- Better performance: Fewer requests during page load results in faster page rendering (first meaningful paint).
|- Must build a server-side solution for capturing fresh datafiles and persisting them to the client side.|
The most common use case for multiple languages is executing variation code on the server and measuring the effects of those changes on the client. Let us run through a basic example of this use case.
This server/client use case is not compatible with secure environments. For more information, see secure environments.
Here is how to get started:
- Set up a new Full Stack project. We will be using our Python backend to assign users to variations, so the project’s primary language should be Python.
- Create a new experiment and conversion event. We’ll create an experiment called
product_list_orderwith two variations,
cheap_first. We’ll also create an event
user_added_to_cartto track the goal of our experiment.
- Figure out if we’ll need to pass user IDs and attributes from the server.
- User IDs: If both the server and client have access to stable, consistent user IDs for everyone in our experiments, we can use that as our user ID. For example, if our experiment will only affect logged-in users, and both the client and server know the user’s unique ID in our database, we could use that as the user ID for our activate calls on the server and track calls on the client. In any other case, we’ll need to pass the user ID from the server.
- User Attributes: Do we want to track any user attributes with our conversion event, and are any of them unavailable on the client? If so, we’ll need to pass them from the server.
- Pass user IDs and/or attributes from the server to the client if necessary. See Sharing user IDs and attributes with client SDKs above.
- Use Activate or Get Feature Enabled on the server to bucket users and execute variation code. In this example, we will activate the
product_list_orderexperiment on our Python server, and sort the product list depending on which variation is returned.
user_added_to_cartevent every time a user adds an item to their cart.
Updated over 1 year ago