Skip to content

Commit d41c775

Browse files
refactor: update DefaultCmabClient to use synchronous fetchDecision method with improved error handling and retry logic
1 parent 7e9487c commit d41c775

File tree

7 files changed

+288
-157
lines changed

7 files changed

+288
-157
lines changed

core-api/src/main/java/com/optimizely/ab/cmab/CmabClient.java renamed to core-api/src/main/java/com/optimizely/ab/cmab/client/CmabClient.java

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,9 @@
1313
* See the License for the specific language governing permissions and
1414
* limitations under the License.
1515
*/
16-
package com.optimizely.ab.cmab;
16+
package com.optimizely.ab.cmab.client;
1717

1818
import java.util.Map;
19-
import java.util.concurrent.CompletableFuture;
2019

2120
public interface CmabClient {
2221
/**
@@ -25,8 +24,8 @@ public interface CmabClient {
2524
* @param ruleId The rule/experiment ID
2625
* @param userId The user ID
2726
* @param attributes User attributes
28-
* @param cmabUuid The CMAB UUID
27+
* @param cmabUUID The CMAB UUID
2928
* @return CompletableFuture containing the variation ID as a String
3029
*/
31-
CompletableFuture<String> fetchDecision(String ruleId, String userId, Map<String, Object> attributes, String cmabUuid);
30+
String fetchDecision(String ruleId, String userId, Map<String, Object> attributes, String cmabUUID);
3231
}

core-api/src/main/java/com/optimizely/ab/cmab/CmabClientConfig.java renamed to core-api/src/main/java/com/optimizely/ab/cmab/client/CmabClientConfig.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
* See the License for the specific language governing permissions and
1414
* limitations under the License.
1515
*/
16-
package com.optimizely.ab.cmab;
16+
package com.optimizely.ab.cmab.client;
1717

1818
import javax.annotation.Nullable;
1919

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
/**
2+
* Copyright 2025, Optimizely
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package com.optimizely.ab.cmab.client;
17+
18+
import com.optimizely.ab.OptimizelyRuntimeException;
19+
20+
public class CmabFetchException extends OptimizelyRuntimeException {
21+
public CmabFetchException(String message) {
22+
super(message);
23+
}
24+
25+
public CmabFetchException(String message, Throwable cause) {
26+
super(message, cause);
27+
}
28+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
/**
2+
* Copyright 2025, Optimizely
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package com.optimizely.ab.cmab.client;
17+
18+
import com.optimizely.ab.OptimizelyRuntimeException;
19+
20+
public class CmabInvalidResponseException extends OptimizelyRuntimeException{
21+
public CmabInvalidResponseException(String message) {
22+
super(message);
23+
}
24+
public CmabInvalidResponseException(String message, Throwable cause) {
25+
super(message, cause);
26+
}
27+
}

core-api/src/main/java/com/optimizely/ab/cmab/RetryConfig.java renamed to core-api/src/main/java/com/optimizely/ab/cmab/client/RetryConfig.java

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -13,23 +13,25 @@
1313
* See the License for the specific language governing permissions and
1414
* limitations under the License.
1515
*/
16-
package com.optimizely.ab.cmab;
16+
package com.optimizely.ab.cmab.client;
1717
/**
1818
* Configuration for retry behavior in CMAB client operations.
1919
*/
2020
public class RetryConfig {
2121
private final int maxRetries;
2222
private final long backoffBaseMs;
2323
private final double backoffMultiplier;
24-
24+
private final int maxTimeoutMs;
25+
2526
/**
2627
* Creates a RetryConfig with custom retry and backoff settings.
2728
*
2829
* @param maxRetries Maximum number of retry attempts
2930
* @param backoffBaseMs Base delay in milliseconds for the first retry
3031
* @param backoffMultiplier Multiplier for exponential backoff (e.g., 2.0 for doubling)
32+
* @param maxTimeoutMs Maximum total timeout in milliseconds for all retry attempts
3133
*/
32-
public RetryConfig(int maxRetries, long backoffBaseMs, double backoffMultiplier) {
34+
public RetryConfig(int maxRetries, long backoffBaseMs, double backoffMultiplier, int maxTimeoutMs) {
3335
if (maxRetries < 0) {
3436
throw new IllegalArgumentException("maxRetries cannot be negative");
3537
}
@@ -39,19 +41,23 @@ public RetryConfig(int maxRetries, long backoffBaseMs, double backoffMultiplier)
3941
if (backoffMultiplier < 1.0) {
4042
throw new IllegalArgumentException("backoffMultiplier must be >= 1.0");
4143
}
44+
if (maxTimeoutMs < 0) {
45+
throw new IllegalArgumentException("maxTimeoutMs cannot be negative");
46+
}
4247

4348
this.maxRetries = maxRetries;
4449
this.backoffBaseMs = backoffBaseMs;
4550
this.backoffMultiplier = backoffMultiplier;
51+
this.maxTimeoutMs = maxTimeoutMs;
4652
}
4753

4854
/**
49-
* Creates a RetryConfig with default backoff settings (1 second base, 2x multiplier).
55+
* Creates a RetryConfig with default backoff settings and timeout (1 second base, 2x multiplier, 10 second timeout).
5056
*
5157
* @param maxRetries Maximum number of retry attempts
5258
*/
5359
public RetryConfig(int maxRetries) {
54-
this(maxRetries, 1000, 2.0); // Default: 1 second base, exponential backoff
60+
this(maxRetries, 1000, 2.0, 10000); // Default: 1 second base, exponential backoff, 10 second timeout
5561
}
5662

5763
/**
@@ -65,7 +71,7 @@ public static RetryConfig defaultConfig() {
6571
* Creates a RetryConfig with no retries (single attempt only).
6672
*/
6773
public static RetryConfig noRetry() {
68-
return new RetryConfig(0);
74+
return new RetryConfig(0, 0, 1.0, 0);
6975
}
7076

7177
public int getMaxRetries() {
@@ -80,6 +86,10 @@ public double getBackoffMultiplier() {
8086
return backoffMultiplier;
8187
}
8288

89+
public int getMaxTimeoutMs() {
90+
return maxTimeoutMs;
91+
}
92+
8393
/**
8494
* Calculates the delay for a specific retry attempt.
8595
*
@@ -95,8 +105,8 @@ public long calculateDelay(int attemptNumber) {
95105

96106
@Override
97107
public String toString() {
98-
return String.format("RetryConfig{maxRetries=%d, backoffBaseMs=%d, backoffMultiplier=%.1f}",
99-
maxRetries, backoffBaseMs, backoffMultiplier);
108+
return String.format("RetryConfig{maxRetries=%d, backoffBaseMs=%d, backoffMultiplier=%.1f, maxTimeoutMs=%d}",
109+
maxRetries, backoffBaseMs, backoffMultiplier, maxTimeoutMs);
100110
}
101111

102112
@Override
@@ -107,6 +117,7 @@ public boolean equals(Object obj) {
107117
RetryConfig that = (RetryConfig) obj;
108118
return maxRetries == that.maxRetries &&
109119
backoffBaseMs == that.backoffBaseMs &&
120+
maxTimeoutMs == that.maxTimeoutMs &&
110121
Double.compare(that.backoffMultiplier, backoffMultiplier) == 0;
111122
}
112123

@@ -115,6 +126,7 @@ public int hashCode() {
115126
int result = maxRetries;
116127
result = 31 * result + Long.hashCode(backoffBaseMs);
117128
result = 31 * result + Double.hashCode(backoffMultiplier);
129+
result = 31 * result + Integer.hashCode(maxTimeoutMs);
118130
return result;
119131
}
120132
}

0 commit comments

Comments
 (0)