Skip to main content
menu_icon.png

Everything you need to switch from Optimizely Classic to X in one place: See the Optimizely X Web Transition Guide.

x
Optimizely Knowledge Base

Working with multiple languages in Full Stack

This article is about Optimizely X.
 
relevant products:
  • Optimizely X Full Stack

THIS ARTICLE WILL HELP YOU:
  • Understand common use cases for multiple languages
  • Demonstrate how to get started with multiple SDKs
  • Address common implementation patterns  

In more complex codebases, a single customer interaction will often involve multiple services and languages. Maybe you want to use a server-side SDK to run an experiment on your backend, but track results with conversion events using one or more client-side SDKs (JavaScript, iOS, or Android). Or perhaps your experiment involves 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 Full Stack provides 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.

Download Full Stack SDKs

If you plan to use multiple SDKs, we strongly recommend using version 2.0 or later of the Full Stack SDKs. Projects created with Full Stack 2.0 generate a v4 datafile with full Feature Management support. This datafile will work with all version 2.0 SDKs.

New projects

When you create a Full Stack SDK project, choose a primary language that matches the language you will use to assign visitors to experiments. In this context, ‘primary language’ means the language your Optimizely UI code samples will be in. For example, if you had a Ruby web backend for bucketing users, an ancillary Python microservice, and iOS and Android clients, you would choose Ruby as your primary language.

After you’ve created your project, create your experiments, install the version 2.0 SDKs, and point them to your project’s datafile.

Existing projects

If you have existing projects created before Full Stack 2.0, 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 the 2.0 SDK and create a new 2.0 Full Stack project as described above.

Consistent bucketing and why it matters

All Optimizely SDKs use MurmurHash3 to determine whether a user is bucketed into an 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).

To better understand how multiple language support works in Full Stack, be sure and read our Knowledge Base article on it’s important to understand how consistent bucketing works in the Full Stack & Mobile SDKs.

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. You can find out more about this in our best practices article on datafile management in Full Stack.

  • 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.

  • 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.

Bucketing ‘at the source’ for server-side experiments

Because of the deterministic and consistent nature of bucketing, it’s 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 doesn’t 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

Sharing user IDs and attributes with client SDKs

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.

While there is no one-size-fits-all approach for this, we recommend using HTTPS-only cookies (i.e. cookies with the Secure flag set). When handling a request from a new user, the server assigns a user ID, buckets the user and runs variation code (if necessary). It then sets a Secure cookie in the response, containing the user ID and any necessary attribute data. The client SDK then reads this cookie and uses it when tracking events. This approach can be used with both the JavaScript and iOS/Android SDKs.

Example: Server-side variation code with client-side conversion tracking

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’s run through a basic example of this use case.

In our example, we have a JavaScript single-page app ecommerce site, and we’re running an experiment in our Python server code on the sorting algorithm for product list. We want to see if this experiment affects how often users add items to their carts on our site. We’ll use the Python SDK to run the experiment and the JavaScript SDK to track conversion events.

Here’s how to get started:

  1. Set up a new Full Stack 2.0 project. We’ll be using our Python backend to assign users to variations, so the project’s primary language should be Python.

  2. Install and set up the latest versions of the necessary SDKs on our server and clients, then point them to our project’s datafile. We’ll install the Python SDK on our server and use npm to install the JavaScript SDK in our single-page app.

  3. Create a new experiment and conversion event. We’ll create an experiment called `product_list_order` with two variations, `expensive_first` and `cheap_first`. We’ll also create an event `user_added_to_cart` to track the goal of our experiment.

  4. 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.

  5. Pass user IDs and/or attributes from the server to the client if necessary. See Sharing user IDs and attributes with client SDKs above.

  6. Use activate on the server to bucket users and execute variation code. In this example, we will activate the `product_list_order` experiment on our Python server, and sort the product list depending on which variation is returned.

  7. Use track on the client to track conversion events. Next, we’d set up our JavaScript client app to track the `user_added_to_cart` event every time a user adds an item to their cart.