Skip to content

Conversation

@FarhanAnjum-opti
Copy link
Contributor

@FarhanAnjum-opti FarhanAnjum-opti commented Sep 24, 2025

Summary

Decision Service methods to handle CMAB

Test plan

Added unit tests

Issues

FSSDK-11170

…ations over CMAB service decisions in DecisionService
Copy link
Contributor

@muzahidul-opti muzahidul-opti left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changes look good to me. Added few comments.

private String cmabUUID;

public DecisionResponse(@Nullable T result, @Nonnull DecisionReasons reasons) {
public DecisionResponse(@Nullable T result, @Nonnull DecisionReasons reasons, @Nonnull boolean error, @Nullable String cmabUUID) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does it impact the existing user?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, existing users can still use this class as they were. In that case cmabUUID will be set null and error will be false. Class behaviour will be same.

Copy link
Contributor

@muzahidul-opti muzahidul-opti left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

Copy link
Contributor

@jaeopt jaeopt left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A few changes in API definitions. Can you refactor them first?
I need more time to review the cmab logic.

String flagKey = flagsWithoutForcedDecision.get(i).getKey();

if (error) {
OptimizelyDecision optimizelyDecision = OptimizelyDecision.newErrorDecision(flagKey, user, DecisionMessage.CMAB_ERROR.reason(experimentKey));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we always report CMAB error for any decision errors? Is this safe?

Copy link
Contributor Author

@FarhanAnjum-opti FarhanAnjum-opti Oct 15, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As far as I understand, we get error from decision service only when cmab fails. So this error flag is only true for cmab errors. @raju-opti can verify.

@Nonnull List<String> keys,
@Nonnull List<OptimizelyDecideOption> options,
boolean ignoreDefaultOptions) {
Map<String, OptimizelyDecision> decisionMap = new HashMap<>();
Copy link
Contributor

@jaeopt jaeopt Oct 13, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We have full separate copies of decideForKeys and decideForKeysSync
It won't be easy to maintain these identical copies with small changes.
Can we consider refactor decideForKeysSync to reuse decideForKeys (or the other way). We can pass "needAsync=True" param to skip async ops (including CMAB).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same for decideSync and decideAllSync?

@Nullable UserProfileTracker userProfileTracker,
@Nullable DecisionReasons reasons) {
@Nullable DecisionReasons reasons,
@Nonnull boolean useCmab) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we change this param useCmab to needAsync (or similar)?
I understand the purpose of this is differentiate sync vs async not specific for cmab.
We can keep more it more generatic for future extension.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We have three cases:

  1. Sync + useCmab (java default)
  2. Async + useCmab (for android)
  3. Sync + witoutCmab (for android)

Using needAsync will be confusing in that case I think.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

useCmab is for differentiating flow in sync path only. Not related to async.

if (decisionMeetAudience.getResult()) {
String bucketingId = getBucketingId(user.getUserId(), user.getAttributes());
String cmabUUID = null;
if (useCmab && isCmabExperiment(experiment)) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can consider to bring useCmab (needAsync flag) to decouple from cmab so can use to cover other async ops too.

Copy link
Contributor

@jaeopt jaeopt left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A few more suggestions at the top level.

@Nonnull List<OptimizelyDecideOption> options,
boolean ignoreDefaultOptions) {
boolean ignoreDefaultOptions,
boolean useCmab) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I still like to change this useCmab to more generic name to async. Let's discuss :)

* @param options A list of options for decision-making.
* @return A decision result.
*/
public OptimizelyDecision decideSync(@Nonnull String key,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we better remove this and all "sync" functions from OptimizelyUserContext.
These are only for android-sdk, so must be hidden from java-sdk users.
Can we change "private final Optimizely optimizely;" in line 53 to "final Optimizely optimizely;" so android-sdk can directly access "optimizely.decideSync()"?

* @param options A list of options for decision-making.
* @return All decision results mapped by flag keys.
*/
public Map<String, OptimizelyDecision> decideForKeysSync(@Nonnull List<String> keys,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same - we should remove this.

* @param options A list of options for decision-making.
* @return All decision results mapped by flag keys.
*/
public Map<String, OptimizelyDecision> decideAllSync(@Nonnull List<OptimizelyDecideOption> options) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same - we should remove this too.

Comment on lines 1954 to 1956
int DEFAULT_MAX_SIZE = 1000;
int DEFAULT_CMAB_CACHE_TIMEOUT = 30 * 60 * 1000;
DefaultLRUCache<CmabCacheValue> cmabCache = new DefaultLRUCache<>(DEFAULT_MAX_SIZE, DEFAULT_CMAB_CACHE_TIMEOUT);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
int DEFAULT_MAX_SIZE = 1000;
int DEFAULT_CMAB_CACHE_TIMEOUT = 30 * 60 * 1000;
DefaultLRUCache<CmabCacheValue> cmabCache = new DefaultLRUCache<>(DEFAULT_MAX_SIZE, DEFAULT_CMAB_CACHE_TIMEOUT);
int DEFAULT_CMAB_CACHE_TIMEOUT_IN_SECS = 30 * 60;
``
I believe DefaultLRUCache accepts timeout in secs not in millisecs. Can we add a test case for DefaultLRUCache to validate this error?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I understand cmabCacheSize and cmabCacheTimeout not configurable for now?

int DEFAULT_CMAB_CACHE_TIMEOUT = 30 * 60 * 1000;
DefaultLRUCache<CmabCacheValue> cmabCache = new DefaultLRUCache<>(DEFAULT_MAX_SIZE, DEFAULT_CMAB_CACHE_TIMEOUT);
CmabServiceOptions cmabServiceOptions = new CmabServiceOptions(logger, cmabCache, cmabClient);
DefaultCmabService defaultCmabService = new DefaultCmabService(cmabServiceOptions);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cmabClient should not be an "option"?
We can consider moving it to DefaultCmabService(cmabClient, cmabServiceOptions).

return this;
}

public Builder withCmabClient(CmabClient cmabClient) {
Copy link
Contributor

@jaeopt jaeopt Oct 17, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@FarhanAnjum-opti It looks like we need to build withCmabService(), so others like android-sdk can inject full-featured CmabService with client + cacheSize + cacheTTL.
We can provide Builder pattern to CmabService like OdpManager -

CmabServiceBuilder() .withCmabCacheSize() .withCmabCachTimeoutInSecs() .withClient() .build()

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants