-
Notifications
You must be signed in to change notification settings - Fork 32
[FSSDK-11143] update: Implement CMAB Client #579
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
FarhanAnjum-opti
merged 18 commits into
master
from
farhan-anjum/FSSDK-11143-implement-cmab-client
Aug 26, 2025
Merged
Changes from 17 commits
Commits
Show all changes
18 commits
Select commit
Hold shift + click to select a range
2640eaa
Cmab datafile parsed
FarhanAnjum-opti ad048df
Merge branch 'master' into farhan-anjum/FSSDK-11134-cmab-datafile-par…
FarhanAnjum-opti 2d21ee2
Add CMAB configuration and parsing tests with cmab datafile
FarhanAnjum-opti 0d3a88d
Add copyright notice to CmabTest and CmabParsingTest files
FarhanAnjum-opti 50334f1
Refactor cmab parsing logic to simplify null check in JsonConfigParser
FarhanAnjum-opti 87c553c
Merge branch 'master' into farhan-anjum/FSSDK-11134-cmab-datafile-par…
FarhanAnjum-opti 8258bb0
update: implement remove method in DefaultLRUCache for cache entry re…
FarhanAnjum-opti 4fa8cbe
add: implement remove method tests in DefaultLRUCacheTest for various…
FarhanAnjum-opti 85b26d4
Merge branch 'master' into farhan-anjum/FSSDK-11152-update-lru-cache-…
FarhanAnjum-opti aa955eb
refactor: remove unused methods from Cache interface
FarhanAnjum-opti d3fc4bb
update: add reset method to Cache interface
FarhanAnjum-opti 044c230
add: implement CmabClient, CmabClientConfig, and RetryConfig with fet…
FarhanAnjum-opti 8eb694e
update: improve error logging in DefaultCmabClient for fetchDecision …
FarhanAnjum-opti 50e2f7d
add: implement unit tests for DefaultCmabClient with various scenario…
FarhanAnjum-opti 254d308
Merge branch 'master' into farhan-anjum/FSSDK-11143-implement-cmab-cl…
FarhanAnjum-opti 04bb6f4
update: add missing license header to DefaultCmabClient.java
FarhanAnjum-opti 7e9487c
update: add missing license headers to CmabClient, CmabClientConfig, …
FarhanAnjum-opti d41c775
refactor: update DefaultCmabClient to use synchronous fetchDecision m…
FarhanAnjum-opti File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
32 changes: 32 additions & 0 deletions
32
core-api/src/main/java/com/optimizely/ab/cmab/CmabClient.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
/** | ||
* Copyright 2025, Optimizely | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* https://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
package com.optimizely.ab.cmab; | ||
|
||
import java.util.Map; | ||
import java.util.concurrent.CompletableFuture; | ||
|
||
public interface CmabClient { | ||
/** | ||
* Fetches a decision from the CMAB prediction service. | ||
* | ||
* @param ruleId The rule/experiment ID | ||
* @param userId The user ID | ||
* @param attributes User attributes | ||
* @param cmabUuid The CMAB UUID | ||
* @return CompletableFuture containing the variation ID as a String | ||
*/ | ||
CompletableFuture<String> fetchDecision(String ruleId, String userId, Map<String, Object> attributes, String cmabUuid); | ||
} |
49 changes: 49 additions & 0 deletions
49
core-api/src/main/java/com/optimizely/ab/cmab/CmabClientConfig.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
/** | ||
* Copyright 2025, Optimizely | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* https://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
package com.optimizely.ab.cmab; | ||
|
||
import javax.annotation.Nullable; | ||
|
||
/** | ||
* Configuration for CMAB client operations. | ||
* Contains only retry configuration since HTTP client is handled separately. | ||
*/ | ||
public class CmabClientConfig { | ||
private final RetryConfig retryConfig; | ||
|
||
public CmabClientConfig(@Nullable RetryConfig retryConfig) { | ||
this.retryConfig = retryConfig; | ||
} | ||
|
||
@Nullable | ||
public RetryConfig getRetryConfig() { | ||
return retryConfig; | ||
} | ||
|
||
/** | ||
* Creates a config with default retry settings. | ||
*/ | ||
public static CmabClientConfig withDefaultRetry() { | ||
return new CmabClientConfig(RetryConfig.defaultConfig()); | ||
} | ||
|
||
/** | ||
* Creates a config with no retry. | ||
*/ | ||
public static CmabClientConfig withNoRetry() { | ||
return new CmabClientConfig(null); | ||
} | ||
} |
120 changes: 120 additions & 0 deletions
120
core-api/src/main/java/com/optimizely/ab/cmab/RetryConfig.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,120 @@ | ||
/** | ||
* Copyright 2025, Optimizely | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* https://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
package com.optimizely.ab.cmab; | ||
/** | ||
* Configuration for retry behavior in CMAB client operations. | ||
*/ | ||
public class RetryConfig { | ||
private final int maxRetries; | ||
private final long backoffBaseMs; | ||
private final double backoffMultiplier; | ||
|
||
FarhanAnjum-opti marked this conversation as resolved.
Show resolved
Hide resolved
|
||
/** | ||
* Creates a RetryConfig with custom retry and backoff settings. | ||
* | ||
* @param maxRetries Maximum number of retry attempts | ||
* @param backoffBaseMs Base delay in milliseconds for the first retry | ||
* @param backoffMultiplier Multiplier for exponential backoff (e.g., 2.0 for doubling) | ||
*/ | ||
public RetryConfig(int maxRetries, long backoffBaseMs, double backoffMultiplier) { | ||
if (maxRetries < 0) { | ||
throw new IllegalArgumentException("maxRetries cannot be negative"); | ||
} | ||
if (backoffBaseMs < 0) { | ||
throw new IllegalArgumentException("backoffBaseMs cannot be negative"); | ||
} | ||
if (backoffMultiplier < 1.0) { | ||
throw new IllegalArgumentException("backoffMultiplier must be >= 1.0"); | ||
} | ||
|
||
this.maxRetries = maxRetries; | ||
this.backoffBaseMs = backoffBaseMs; | ||
this.backoffMultiplier = backoffMultiplier; | ||
} | ||
|
||
/** | ||
* Creates a RetryConfig with default backoff settings (1 second base, 2x multiplier). | ||
* | ||
* @param maxRetries Maximum number of retry attempts | ||
*/ | ||
public RetryConfig(int maxRetries) { | ||
this(maxRetries, 1000, 2.0); // Default: 1 second base, exponential backoff | ||
} | ||
|
||
/** | ||
* Creates a default RetryConfig with 3 retries and exponential backoff. | ||
*/ | ||
public static RetryConfig defaultConfig() { | ||
return new RetryConfig(3); | ||
} | ||
|
||
/** | ||
* Creates a RetryConfig with no retries (single attempt only). | ||
*/ | ||
public static RetryConfig noRetry() { | ||
return new RetryConfig(0); | ||
} | ||
|
||
public int getMaxRetries() { | ||
return maxRetries; | ||
} | ||
|
||
public long getBackoffBaseMs() { | ||
return backoffBaseMs; | ||
} | ||
|
||
public double getBackoffMultiplier() { | ||
return backoffMultiplier; | ||
} | ||
|
||
/** | ||
* Calculates the delay for a specific retry attempt. | ||
* | ||
* @param attemptNumber The attempt number (0-based, so 0 = first retry) | ||
* @return Delay in milliseconds | ||
*/ | ||
public long calculateDelay(int attemptNumber) { | ||
if (attemptNumber < 0) { | ||
return 0; | ||
} | ||
return (long) (backoffBaseMs * Math.pow(backoffMultiplier, attemptNumber)); | ||
FarhanAnjum-opti marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
|
||
@Override | ||
public String toString() { | ||
return String.format("RetryConfig{maxRetries=%d, backoffBaseMs=%d, backoffMultiplier=%.1f}", | ||
maxRetries, backoffBaseMs, backoffMultiplier); | ||
} | ||
|
||
@Override | ||
public boolean equals(Object obj) { | ||
if (this == obj) return true; | ||
if (obj == null || getClass() != obj.getClass()) return false; | ||
|
||
RetryConfig that = (RetryConfig) obj; | ||
return maxRetries == that.maxRetries && | ||
backoffBaseMs == that.backoffBaseMs && | ||
Double.compare(that.backoffMultiplier, backoffMultiplier) == 0; | ||
} | ||
|
||
@Override | ||
public int hashCode() { | ||
int result = maxRetries; | ||
result = 31 * result + Long.hashCode(backoffBaseMs); | ||
result = 31 * result + Double.hashCode(backoffMultiplier); | ||
return result; | ||
} | ||
} |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.