Skip to content

Commit fe50318

Browse files
authored
[FSSDK-10762] Implement UPS request batching for decideForKeys (#549)
1 parent e813bfe commit fe50318

File tree

5 files changed

+458
-141
lines changed

5 files changed

+458
-141
lines changed

core-api/src/main/java/com/optimizely/ab/Optimizely.java

+87-41
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/****************************************************************************
2-
* Copyright 2016-2023, Optimizely, Inc. and contributors *
2+
* Copyright 2016-2024, Optimizely, Inc. and contributors *
33
* *
44
* Licensed under the Apache License, Version 2.0 (the "License"); *
55
* you may not use this file except in compliance with the License. *
@@ -42,8 +42,10 @@
4242
import javax.annotation.Nonnull;
4343
import javax.annotation.Nullable;
4444
import javax.annotation.concurrent.ThreadSafe;
45+
4546
import java.io.Closeable;
4647
import java.util.*;
48+
import java.util.stream.Collectors;
4749

4850
import static com.optimizely.ab.internal.SafetyUtils.tryClose;
4951

@@ -1194,55 +1196,39 @@ private OptimizelyUserContext createUserContextCopy(@Nonnull String userId, @Non
11941196
OptimizelyDecision decide(@Nonnull OptimizelyUserContext user,
11951197
@Nonnull String key,
11961198
@Nonnull List<OptimizelyDecideOption> options) {
1197-
11981199
ProjectConfig projectConfig = getProjectConfig();
11991200
if (projectConfig == null) {
12001201
return OptimizelyDecision.newErrorDecision(key, user, DecisionMessage.SDK_NOT_READY.reason());
12011202
}
12021203

1203-
FeatureFlag flag = projectConfig.getFeatureKeyMapping().get(key);
1204-
if (flag == null) {
1205-
return OptimizelyDecision.newErrorDecision(key, user, DecisionMessage.FLAG_KEY_INVALID.reason(key));
1206-
}
1207-
1208-
String userId = user.getUserId();
1209-
Map<String, Object> attributes = user.getAttributes();
1210-
Boolean decisionEventDispatched = false;
12111204
List<OptimizelyDecideOption> allOptions = getAllOptions(options);
1212-
DecisionReasons decisionReasons = DefaultDecisionReasons.newInstance(allOptions);
1205+
allOptions.remove(OptimizelyDecideOption.ENABLED_FLAGS_ONLY);
12131206

1214-
Map<String, ?> copiedAttributes = new HashMap<>(attributes);
1215-
FeatureDecision flagDecision;
1216-
1217-
// Check Forced Decision
1218-
OptimizelyDecisionContext optimizelyDecisionContext = new OptimizelyDecisionContext(flag.getKey(), null);
1219-
DecisionResponse<Variation> forcedDecisionVariation = decisionService.validatedForcedDecision(optimizelyDecisionContext, projectConfig, user);
1220-
decisionReasons.merge(forcedDecisionVariation.getReasons());
1221-
if (forcedDecisionVariation.getResult() != null) {
1222-
flagDecision = new FeatureDecision(null, forcedDecisionVariation.getResult(), FeatureDecision.DecisionSource.FEATURE_TEST);
1223-
} else {
1224-
// Regular decision
1225-
DecisionResponse<FeatureDecision> decisionVariation = decisionService.getVariationForFeature(
1226-
flag,
1227-
user,
1228-
projectConfig,
1229-
allOptions);
1230-
flagDecision = decisionVariation.getResult();
1231-
decisionReasons.merge(decisionVariation.getReasons());
1232-
}
1207+
return decideForKeys(user, Arrays.asList(key), allOptions, true).get(key);
1208+
}
1209+
1210+
private OptimizelyDecision createOptimizelyDecision(
1211+
OptimizelyUserContext user,
1212+
String flagKey,
1213+
FeatureDecision flagDecision,
1214+
DecisionReasons decisionReasons,
1215+
List<OptimizelyDecideOption> allOptions,
1216+
ProjectConfig projectConfig
1217+
) {
1218+
String userId = user.getUserId();
12331219

12341220
Boolean flagEnabled = false;
12351221
if (flagDecision.variation != null) {
12361222
if (flagDecision.variation.getFeatureEnabled()) {
12371223
flagEnabled = true;
12381224
}
12391225
}
1240-
logger.info("Feature \"{}\" is enabled for user \"{}\"? {}", key, userId, flagEnabled);
1226+
logger.info("Feature \"{}\" is enabled for user \"{}\"? {}", flagKey, userId, flagEnabled);
12411227

12421228
Map<String, Object> variableMap = new HashMap<>();
12431229
if (!allOptions.contains(OptimizelyDecideOption.EXCLUDE_VARIABLES)) {
12441230
DecisionResponse<Map<String, Object>> decisionVariables = getDecisionVariableMap(
1245-
flag,
1231+
projectConfig.getFeatureKeyMapping().get(flagKey),
12461232
flagDecision.variation,
12471233
flagEnabled);
12481234
variableMap = decisionVariables.getResult();
@@ -1261,22 +1247,28 @@ OptimizelyDecision decide(@Nonnull OptimizelyUserContext user,
12611247
// add to event metadata as well (currently set to experimentKey)
12621248
String ruleKey = flagDecision.experiment != null ? flagDecision.experiment.getKey() : null;
12631249

1250+
1251+
Boolean decisionEventDispatched = false;
1252+
1253+
Map<String, Object> attributes = user.getAttributes();
1254+
Map<String, ?> copiedAttributes = new HashMap<>(attributes);
1255+
12641256
if (!allOptions.contains(OptimizelyDecideOption.DISABLE_DECISION_EVENT)) {
12651257
decisionEventDispatched = sendImpression(
12661258
projectConfig,
12671259
flagDecision.experiment,
12681260
userId,
12691261
copiedAttributes,
12701262
flagDecision.variation,
1271-
key,
1263+
flagKey,
12721264
decisionSource.toString(),
12731265
flagEnabled);
12741266
}
12751267

12761268
DecisionNotification decisionNotification = DecisionNotification.newFlagDecisionNotificationBuilder()
12771269
.withUserId(userId)
12781270
.withAttributes(copiedAttributes)
1279-
.withFlagKey(key)
1271+
.withFlagKey(flagKey)
12801272
.withEnabled(flagEnabled)
12811273
.withVariables(variableMap)
12821274
.withVariationKey(variationKey)
@@ -1291,30 +1283,84 @@ OptimizelyDecision decide(@Nonnull OptimizelyUserContext user,
12911283
flagEnabled,
12921284
optimizelyJSON,
12931285
ruleKey,
1294-
key,
1286+
flagKey,
12951287
user,
12961288
reasonsToReport);
12971289
}
12981290

12991291
Map<String, OptimizelyDecision> decideForKeys(@Nonnull OptimizelyUserContext user,
1292+
@Nonnull List<String> keys,
1293+
@Nonnull List<OptimizelyDecideOption> options) {
1294+
return decideForKeys(user, keys, options, false);
1295+
}
1296+
1297+
private Map<String, OptimizelyDecision> decideForKeys(@Nonnull OptimizelyUserContext user,
13001298
@Nonnull List<String> keys,
1301-
@Nonnull List<OptimizelyDecideOption> options) {
1299+
@Nonnull List<OptimizelyDecideOption> options,
1300+
boolean ignoreDefaultOptions) {
13021301
Map<String, OptimizelyDecision> decisionMap = new HashMap<>();
13031302

13041303
ProjectConfig projectConfig = getProjectConfig();
13051304
if (projectConfig == null) {
1306-
logger.error("Optimizely instance is not valid, failing isFeatureEnabled call.");
1305+
logger.error("Optimizely instance is not valid, failing decideForKeys call.");
13071306
return decisionMap;
13081307
}
13091308

13101309
if (keys.isEmpty()) return decisionMap;
13111310

1312-
List<OptimizelyDecideOption> allOptions = getAllOptions(options);
1311+
List<OptimizelyDecideOption> allOptions = ignoreDefaultOptions ? options: getAllOptions(options);
1312+
1313+
Map<String, FeatureDecision> flagDecisions = new HashMap<>();
1314+
Map<String, DecisionReasons> decisionReasonsMap = new HashMap<>();
1315+
1316+
List<FeatureFlag> flagsWithoutForcedDecision = new ArrayList<>();
1317+
1318+
List<String> validKeys = new ArrayList<>();
13131319

13141320
for (String key : keys) {
1315-
OptimizelyDecision decision = decide(user, key, options);
1316-
if (!allOptions.contains(OptimizelyDecideOption.ENABLED_FLAGS_ONLY) || decision.getEnabled()) {
1317-
decisionMap.put(key, decision);
1321+
FeatureFlag flag = projectConfig.getFeatureKeyMapping().get(key);
1322+
if (flag == null) {
1323+
decisionMap.put(key, OptimizelyDecision.newErrorDecision(key, user, DecisionMessage.FLAG_KEY_INVALID.reason(key)));
1324+
continue;
1325+
}
1326+
1327+
validKeys.add(key);
1328+
1329+
DecisionReasons decisionReasons = DefaultDecisionReasons.newInstance(allOptions);
1330+
decisionReasonsMap.put(key, decisionReasons);
1331+
1332+
OptimizelyDecisionContext optimizelyDecisionContext = new OptimizelyDecisionContext(key, null);
1333+
DecisionResponse<Variation> forcedDecisionVariation = decisionService.validatedForcedDecision(optimizelyDecisionContext, projectConfig, user);
1334+
decisionReasons.merge(forcedDecisionVariation.getReasons());
1335+
1336+
if (forcedDecisionVariation.getResult() != null) {
1337+
flagDecisions.put(key,
1338+
new FeatureDecision(null, forcedDecisionVariation.getResult(), FeatureDecision.DecisionSource.FEATURE_TEST));
1339+
} else {
1340+
flagsWithoutForcedDecision.add(flag);
1341+
}
1342+
}
1343+
1344+
List<DecisionResponse<FeatureDecision>> decisionList =
1345+
decisionService.getVariationsForFeatureList(flagsWithoutForcedDecision, user, projectConfig, allOptions);
1346+
1347+
for (int i = 0; i < flagsWithoutForcedDecision.size(); i++) {
1348+
DecisionResponse<FeatureDecision> decision = decisionList.get(i);
1349+
String flagKey = flagsWithoutForcedDecision.get(i).getKey();
1350+
flagDecisions.put(flagKey, decision.getResult());
1351+
decisionReasonsMap.get(flagKey).merge(decision.getReasons());
1352+
}
1353+
1354+
for (String key: validKeys) {
1355+
FeatureDecision flagDecision = flagDecisions.get(key);
1356+
DecisionReasons decisionReasons = decisionReasonsMap.get((key));
1357+
1358+
OptimizelyDecision optimizelyDecision = createOptimizelyDecision(
1359+
user, key, flagDecision, decisionReasons, allOptions, projectConfig
1360+
);
1361+
1362+
if (!allOptions.contains(OptimizelyDecideOption.ENABLED_FLAGS_ONLY) || optimizelyDecision.getEnabled()) {
1363+
decisionMap.put(key, optimizelyDecision);
13181364
}
13191365
}
13201366

0 commit comments

Comments
 (0)