Skip to content

Commit 044c230

Browse files
add: implement CmabClient, CmabClientConfig, and RetryConfig with fetchDecision method and retry logic
1 parent d3fc4bb commit 044c230

File tree

4 files changed

+427
-0
lines changed

4 files changed

+427
-0
lines changed
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package com.optimizely.ab.cmab;
2+
3+
import java.util.Map;
4+
import java.util.concurrent.CompletableFuture;
5+
6+
public interface CmabClient {
7+
/**
8+
* Fetches a decision from the CMAB prediction service.
9+
*
10+
* @param ruleId The rule/experiment ID
11+
* @param userId The user ID
12+
* @param attributes User attributes
13+
* @param cmabUuid The CMAB UUID
14+
* @return CompletableFuture containing the variation ID as a String
15+
*/
16+
CompletableFuture<String> fetchDecision(String ruleId, String userId, Map<String, Object> attributes, String cmabUuid);
17+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
package com.optimizely.ab.cmab;
2+
3+
import javax.annotation.Nullable;
4+
5+
/**
6+
* Configuration for CMAB client operations.
7+
* Contains only retry configuration since HTTP client is handled separately.
8+
*/
9+
public class CmabClientConfig {
10+
private final RetryConfig retryConfig;
11+
12+
public CmabClientConfig(@Nullable RetryConfig retryConfig) {
13+
this.retryConfig = retryConfig;
14+
}
15+
16+
@Nullable
17+
public RetryConfig getRetryConfig() {
18+
return retryConfig;
19+
}
20+
21+
/**
22+
* Creates a config with default retry settings.
23+
*/
24+
public static CmabClientConfig withDefaultRetry() {
25+
return new CmabClientConfig(RetryConfig.defaultConfig());
26+
}
27+
28+
/**
29+
* Creates a config with no retry.
30+
*/
31+
public static CmabClientConfig withNoRetry() {
32+
return new CmabClientConfig(null);
33+
}
34+
}
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
package com.optimizely.ab.cmab;
2+
/**
3+
* Configuration for retry behavior in CMAB client operations.
4+
*/
5+
public class RetryConfig {
6+
private final int maxRetries;
7+
private final long backoffBaseMs;
8+
private final double backoffMultiplier;
9+
10+
/**
11+
* Creates a RetryConfig with custom retry and backoff settings.
12+
*
13+
* @param maxRetries Maximum number of retry attempts
14+
* @param backoffBaseMs Base delay in milliseconds for the first retry
15+
* @param backoffMultiplier Multiplier for exponential backoff (e.g., 2.0 for doubling)
16+
*/
17+
public RetryConfig(int maxRetries, long backoffBaseMs, double backoffMultiplier) {
18+
if (maxRetries < 0) {
19+
throw new IllegalArgumentException("maxRetries cannot be negative");
20+
}
21+
if (backoffBaseMs < 0) {
22+
throw new IllegalArgumentException("backoffBaseMs cannot be negative");
23+
}
24+
if (backoffMultiplier < 1.0) {
25+
throw new IllegalArgumentException("backoffMultiplier must be >= 1.0");
26+
}
27+
28+
this.maxRetries = maxRetries;
29+
this.backoffBaseMs = backoffBaseMs;
30+
this.backoffMultiplier = backoffMultiplier;
31+
}
32+
33+
/**
34+
* Creates a RetryConfig with default backoff settings (1 second base, 2x multiplier).
35+
*
36+
* @param maxRetries Maximum number of retry attempts
37+
*/
38+
public RetryConfig(int maxRetries) {
39+
this(maxRetries, 1000, 2.0); // Default: 1 second base, exponential backoff
40+
}
41+
42+
/**
43+
* Creates a default RetryConfig with 3 retries and exponential backoff.
44+
*/
45+
public static RetryConfig defaultConfig() {
46+
return new RetryConfig(3);
47+
}
48+
49+
/**
50+
* Creates a RetryConfig with no retries (single attempt only).
51+
*/
52+
public static RetryConfig noRetry() {
53+
return new RetryConfig(0);
54+
}
55+
56+
public int getMaxRetries() {
57+
return maxRetries;
58+
}
59+
60+
public long getBackoffBaseMs() {
61+
return backoffBaseMs;
62+
}
63+
64+
public double getBackoffMultiplier() {
65+
return backoffMultiplier;
66+
}
67+
68+
/**
69+
* Calculates the delay for a specific retry attempt.
70+
*
71+
* @param attemptNumber The attempt number (0-based, so 0 = first retry)
72+
* @return Delay in milliseconds
73+
*/
74+
public long calculateDelay(int attemptNumber) {
75+
if (attemptNumber < 0) {
76+
return 0;
77+
}
78+
return (long) (backoffBaseMs * Math.pow(backoffMultiplier, attemptNumber));
79+
}
80+
81+
@Override
82+
public String toString() {
83+
return String.format("RetryConfig{maxRetries=%d, backoffBaseMs=%d, backoffMultiplier=%.1f}",
84+
maxRetries, backoffBaseMs, backoffMultiplier);
85+
}
86+
87+
@Override
88+
public boolean equals(Object obj) {
89+
if (this == obj) return true;
90+
if (obj == null || getClass() != obj.getClass()) return false;
91+
92+
RetryConfig that = (RetryConfig) obj;
93+
return maxRetries == that.maxRetries &&
94+
backoffBaseMs == that.backoffBaseMs &&
95+
Double.compare(that.backoffMultiplier, backoffMultiplier) == 0;
96+
}
97+
98+
@Override
99+
public int hashCode() {
100+
int result = maxRetries;
101+
result = 31 * result + Long.hashCode(backoffBaseMs);
102+
result = 31 * result + Double.hashCode(backoffMultiplier);
103+
return result;
104+
}
105+
}

0 commit comments

Comments
 (0)