Optimizely Feature Flag Migration Guide
-
Install the Eppo SDK
-
Login to Eppo with your work email: https://eppo.cloud/
-
Generate an SDK key by navigating to “SDK Keys” under Configuration
-
Define a logging function for the Eppo SDK to log assignments so they end up in your data warehouse
TypeScript Example:
const assignmentLogger: IAssignmentLogger = {
logAssignment(assignment) {
analytics.track({
userId: assignment.subject,
event: "Eppo Randomization Event",
type: "track",
properties: { ...assignment },
});
},
}; -
Initialize the SDK in your code using the SDK guides for your language here
TypeScript Example:
await init({
apiKey: EPPO_SDK_KEY,
assignmentLogger,
});
-
-
Set up a new flag and verify its functionality
-
Create a new flag in Eppo by navigating to “Feature Flags” under Configuration
-
Implement the flag in your application code
-
Test the flag in your local development environment to ensure it works as expected.
TypeScript Example:
const variation = getInstance().getStringAssignment(
"<SUBJECT-KEY>",
"<FLAG-KEY>",
{
// Optional map of subject metadata for targeting.
}
); -
Deploy the application to your staging or testing environments and verify the flag's functionality.
-
Once verified, deploy the application to your production environment and test the flag again.
-
-
Identify critical flags in Optimizely
- Make a list of all the feature flags currently in use within your application using the provided template
- Categorize the flags as critical or non-critical based on their importance and impact on your application's functionality.
- Flags that are disabled or are rolled out to 100% can be categorized as non-critical
-
Remove existing flag code for all non-critical flags
- For the non-critical flags identified in the previous step, remove the flag code from your application.
- Test your application thoroughly to ensure that the removal of these flags does not introduce any regressions or unintended behavior.
-
Create a fallback value for critical flags
-
Implement a function that wraps calling Eppo’s SDK to have a fallback mechanism to use the Optimizely flag values if the new service is unavailable or experiences issues.
-
When attempting to retrieve a flag value from Eppo, catch any exceptions or errors that may occur due to service unavailability or issues and return the old value.
TypeScript Example:
// After initialization, turn off graceful failure so exceptions are rethrown
getInstance().setIsGracefulFailureMode(false);
// Drop-in wrapper replacement for isFeatureEnabled()
export function isFeatureEnabledWrapper(
featureKey: string,
userId: string,
attributes?: Record<string, string | number | boolean | null>
) {
let isEnabled = false;
try {
// For grouping values with a single flag, JSON-typed variations are used
isEnabled = getInstance().getBooleanAssignment(
userId,
featureKey,
attributes
) ?? false;
} catch (e) {
logger.warn(
'Error encountered evaluating boolean assignment from Eppo SDK; falling back to optimizely',
{ featureKey, userId, attributes }
);
isEnabled = optimizelyClientInstance.isFeatureEnabled(
featureKey,
userId,
attributes
);
}
return isEnabled;
}
// Drop-in wrapper replacement for getFeatureVariableString()
export function getFeatureVariableStringWrapper(
featureKey: string,
variableKey: string,
userId: string,
attributes?: Record<string, string | number | boolean | null>
) {
let assignment: string | null = null;
try {
// For grouping values with a single flag, JSON-typed variations are used
const featureVariables = getInstance().getParsedJSONAssignment(
userId,
featureKey,
attributes
) as Record<string, string | number | boolean | null>;
// Look up the desired specific value
assignment = featureVariables?.[variableKey]?.toString() ?? null;
} catch (e) {
logger.warn(
'Error encountered evaluating assignment from Eppo SDK; falling back to optimizely',
{ featureKey, variableKey, userId, attributes }
);
assignment = optimizelyClientInstance.getFeatureVariableString(
featureKey,
variableKey,
userId,
attributes
);
}
return assignment;
}
-
-
Recreate existing flags in Eppo
- In the Eppo dashboard, recreate the critical flags from Optimizely.
- Ensure that the flag configurations, such as rollout percentages, targeting rules, and variations, are accurately replicated in the new service.
-
Switch existing flags to the new application
-
Once you have verified that the Eppo flags are working correctly, switch your application to use the function that checks Eppo for flags instead of the Optimizely ones.
-
Remove the fallback mechanism and the Optimizely flag code once you have confirmed that the Eppo flags are working as expected in production.
-
It’s recommended to keep the wrapper as a facade to make future changes easier, as they will typically only need to be made to the wrapper.
TypeScript Example:
// FeatureHelper.ts
export function isFeatureEnabledWrapper(
featureKey: string,
userId: string,
attributes?: Record<string, string | number | boolean | null>
) {
return getInstance().getBooleanAssignment(userId, featureKey, attributes) ?? false;
}// PlaceUsingFlags.ts
const useBigButtons = isFeatureEnabledWrapper(userId, 'use-big-buttons', userAttributes);
Appendix: TypeScript Implementation Comparison
Optimizely and Eppo have very similar interfaces, making switching from one to the other straightforward.
Note: Above each code example is a link to its respective documentation source.
Initialization
const optimizelyClientInstance = optimizelySdk.createInstance({
datafile: window.optimizelyDatafile,
});Eppo:
await init({
apiKey: EPPO_SDK_KEY
});Wiring Up Assignment Logger
optimizely.notificationCenter.addDecisionNotificationListener(decisionListener: { (type, userId, attributes, decisionInfo) in
// Send data to analytics provider / warehouse here
})Eppo:
const assignmentLogger: IAssignmentLogger = {
logAssignment(assignment) {
// Send data to analytics provider / warehouse here
}
};
getInstance().setLogger(assignmentLogger); // Note: can also be set in init()Getting a Boolean Flag
For example, checking if a feature is enabled
const enabled =
optimizelyClientInstance.isFeatureEnabled(featureKey, userId, attributes);*Eppo:*
const enabled =
getInstance().getBooleanAssignment(userId, featureKey, attributes) ?? false;Getting a String Value
const value =
getFeatureVariableString(featureKey, variableKey, userId, attributes)Eppo:
// If it's part of a multi-valued variation (How Optimizely organizes values)
const value =
getInstance().getParsedJSONAssignment(userId, featureKey, attributes)?.[variableKey];
// If it's a stand-alone string variation value (Eppo only)
const value =
getInstance().getStringAssignment(userId, featureKey, attributes);Getting All Values of a Multi-Valued Flag Variation
For example, getting all variables for a feature
const values =
optimizelyClient.getAllFeatureVariables(featureKey, userId, attributes);Eppo:
const values =
getInstance().getParsedJSONAssignment(userId, featureKey, attributes); -