Skip to content

Commit 3e1c868

Browse files
Kaustubh22327rohitesh-wingify
authored andcommitted
feat: getFlag Batching
1 parent 536bb84 commit 3e1c868

9 files changed

Lines changed: 178 additions & 33 deletions

File tree

CHANGELOG.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,16 @@ All notable changes to this project will be documented in this file.
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8+
## [1.17.0] - 2025-01-19
9+
10+
### Added
11+
12+
- Implemented impression batching for `getFlag` API to reduce network latency
13+
- Collects all impression payloads (rollout, experiment, whitelisting, impact campaign) during evaluation
14+
- Sends all impressions in a single async batch request at the end of `getFlag`
15+
- Reduces from 3 sequential network calls to 1 batch call
16+
- Maintains immediate send behavior when gateway service is configured
17+
818
## [1.16.0] - 2026-01-15
919

1020
- Added support for redirecting all network calls through a custom proxy URL. This feature allows users to route all SDK network requests (settings, tracking, etc.) through their own proxy server.

pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ limitations under the License. -->
1919

2020
<groupId>com.vwo.sdk</groupId>
2121
<artifactId>vwo-fme-java-sdk</artifactId>
22-
<version>1.16.0</version>
22+
<version>1.17.0</version>
2323
<packaging>jar</packaging>
2424

2525
<name>VWO FME Java SDK</name>

src/main/java/com/vwo/ServiceContainer.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,7 @@ public Settings getSettings() {
123123
*/
124124
public String getEndpointWithCollectionPrefix(String endpoint) {
125125
if (this.settingsManager.collectionPrefix != null && !this.settingsManager.collectionPrefix.isEmpty()) {
126-
return "/" + this.settingsManager.collectionPrefix + "/" + endpoint;
126+
return "/" + this.settingsManager.collectionPrefix + endpoint;
127127
}
128128

129129
return endpoint;

src/main/java/com/vwo/api/GetFlagAPI.java

Lines changed: 81 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,21 +21,25 @@
2121
import com.vwo.enums.ApiEnum;
2222
import com.vwo.enums.CampaignTypeEnum;
2323
import com.vwo.enums.DebuggerCategoryEnum;
24+
import com.vwo.enums.EventEnum;
2425
import com.vwo.constants.Constants;
2526
import com.vwo.models.*;
27+
import com.vwo.models.request.EventArchPayload;
2628
import com.vwo.models.user.GetFlag;
2729
import com.vwo.models.user.VWOContext;
2830
import com.vwo.packages.logger.enums.LogLevelEnum;
2931
import com.vwo.services.StorageService;
3032
import com.vwo.utils.DebuggerServiceUtil;
33+
import com.vwo.utils.NetworkUtil;
3134
import com.vwo.utils.RuleEvaluationUtil;
3235

3336
import java.util.*;
3437

3538
import static com.vwo.utils.CampaignUtil.getVariationFromCampaignKey;
3639
import static com.vwo.utils.DecisionUtil.evaluateTrafficAndGetVariation;
3740
import static com.vwo.utils.FunctionUtil.*;
38-
import static com.vwo.utils.ImpressionUtil.createAndSendImpressionForVariationShown;
41+
import static com.vwo.utils.ImpressionUtil.sendImpressionForVariationShown;
42+
import static com.vwo.utils.ImpressionUtil.sendImpressionForVariationShownInBatch;
3943

4044
public class GetFlagAPI {
4145

@@ -52,6 +56,7 @@ public static GetFlag getFlag(String featureKey, VWOContext context, ServiceCont
5256

5357
Map<String, Object> passedRulesInformation = new HashMap<>();
5458
Map<String, Object> evaluatedFeatureMap = new HashMap<>();
59+
List<EventArchPayload> batchPayloads = new ArrayList<>();
5560

5661
// get feature object from feature key
5762
Feature feature = getFeatureFromKey(serviceContainer.getSettings(), featureKey);
@@ -184,7 +189,24 @@ public static GetFlag getFlag(String featureKey, VWOContext context, ServiceCont
184189
getFlag.setVariables(variation.getVariables());
185190
shouldCheckForExperimentsRules = true;
186191
updateIntegrationsDecisionObject(passedRolloutCampaign, variation, passedRulesInformation, decision);
187-
createAndSendImpressionForVariationShown(serviceContainer, passedRolloutCampaign.getId(), variation.getId(), context);
192+
193+
// Create payload for sending
194+
EventArchPayload payload = NetworkUtil.getTrackUserPayloadData(
195+
serviceContainer,
196+
context.getId(),
197+
EventEnum.VWO_VARIATION_SHOWN.getValue(),
198+
passedRolloutCampaign.getId(),
199+
variation.getId(),
200+
context.getUserAgent(),
201+
context.getIpAddress()
202+
);
203+
if (serviceContainer.getSettingsManager().isGatewayServiceProvided && payload != null) {
204+
// Gateway service: send immediately
205+
sendImpressionForVariationShown(serviceContainer, passedRolloutCampaign.getId(), variation.getId(), context, payload);
206+
} else if (payload != null) {
207+
// Non-gateway: add to batch
208+
batchPayloads.add(payload);
209+
}
188210
}
189211
}
190212
} else if (!shouldCheckForExperimentsRules) {
@@ -212,12 +234,24 @@ public static GetFlag getFlag(String featureKey, VWOContext context, ServiceCont
212234
if (whitelistedObject == null) {
213235
experimentRulesToEvaluate.add(rule);
214236
} else {
215-
// If whitelisted object is not null, update the decision object and send an impression
237+
// If whitelisted object is not null, update the decision object and handle payload
216238
getFlag.setIsEnabled(true);
217239
getFlag.setVariables(whitelistedObject.getVariables());
218240
passedRulesInformation.put("experimentId", rule.getId());
219241
passedRulesInformation.put("experimentKey", rule.getKey());
220242
passedRulesInformation.put("experimentVariationId", whitelistedObject.getId());
243+
244+
// Handle whitelisting payload
245+
EventArchPayload whitelistPayload = (EventArchPayload) evaluateRuleResult.get("payload");
246+
if (whitelistPayload != null) {
247+
if (serviceContainer.getSettingsManager().isGatewayServiceProvided) {
248+
// Gateway service: send immediately
249+
sendImpressionForVariationShown(serviceContainer, rule.getId(), whitelistedObject.getId(), context, whitelistPayload);
250+
} else {
251+
// Non-gateway: add to batch
252+
batchPayloads.add(whitelistPayload);
253+
}
254+
}
221255
}
222256
break;
223257
}
@@ -231,7 +265,24 @@ public static GetFlag getFlag(String featureKey, VWOContext context, ServiceCont
231265
getFlag.setIsEnabled(true);
232266
getFlag.setVariables(variation.getVariables());
233267
updateIntegrationsDecisionObject(campaign, variation, passedRulesInformation, decision);
234-
createAndSendImpressionForVariationShown(serviceContainer, campaign.getId(), variation.getId(), context);
268+
269+
// Create payload for sending
270+
EventArchPayload payload = NetworkUtil.getTrackUserPayloadData(
271+
serviceContainer,
272+
context.getId(),
273+
EventEnum.VWO_VARIATION_SHOWN.getValue(),
274+
campaign.getId(),
275+
variation.getId(),
276+
context.getUserAgent(),
277+
context.getIpAddress()
278+
);
279+
if (serviceContainer.getSettingsManager().isGatewayServiceProvided && payload != null) {
280+
// Gateway service: send immediately
281+
sendImpressionForVariationShown(serviceContainer, campaign.getId(), variation.getId(), context, payload);
282+
} else if (payload != null) {
283+
// Non-gateway: add to batch
284+
batchPayloads.add(payload);
285+
}
235286
}
236287
}
237288
}
@@ -266,13 +317,37 @@ public static GetFlag getFlag(String featureKey, VWOContext context, ServiceCont
266317
put("status", getFlag.isEnabled() ? "enabled": "disabled");
267318
}
268319
});
269-
createAndSendImpressionForVariationShown(
320+
321+
// Create payload for impact campaign
322+
EventArchPayload impactPayload = NetworkUtil.getTrackUserPayloadData(
270323
serviceContainer,
324+
context.getId(),
325+
EventEnum.VWO_VARIATION_SHOWN.getValue(),
271326
feature.getImpactCampaign().getCampaignId(),
272327
getFlag.isEnabled() ? 2 : 1,
273-
context
328+
context.getUserAgent(),
329+
context.getIpAddress()
274330
);
331+
if (serviceContainer.getSettingsManager().isGatewayServiceProvided && impactPayload != null) {
332+
// Gateway service: send immediately
333+
sendImpressionForVariationShown(
334+
serviceContainer,
335+
feature.getImpactCampaign().getCampaignId(),
336+
getFlag.isEnabled() ? 2 : 1,
337+
context,
338+
impactPayload
339+
);
340+
} else if (impactPayload != null) {
341+
// Non-gateway: add to batch
342+
batchPayloads.add(impactPayload);
343+
}
275344
}
345+
346+
// Send all collected payloads in a single batch request
347+
if (!batchPayloads.isEmpty() && !serviceContainer.getSettingsManager().isGatewayServiceProvided) {
348+
sendImpressionForVariationShownInBatch(batchPayloads, serviceContainer);
349+
}
350+
276351
return getFlag;
277352
}
278353

src/main/java/com/vwo/constants/Constants.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ public class Constants {
2828
public static final int DEFAULT_REQUEST_TIME_INTERVAL = 600; // 10 * 60(secs) = 600 secs i.e. 10 minutes
2929
public static final int DEFAULT_EVENTS_PER_REQUEST = 100;
3030
public static final String SDK_NAME = "vwo-fme-java-sdk";
31-
public static final String SDK_VERSION = "1.16.0";
31+
public static final String SDK_VERSION = "1.17.0";
3232
public static final long SETTINGS_EXPIRY = 10000000;
3333
public static final long SETTINGS_TIMEOUT = 50000;
3434

src/main/java/com/vwo/utils/ImpressionUtil.java

Lines changed: 68 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -15,49 +15,45 @@
1515
*/
1616
package com.vwo.utils;
1717

18+
import com.vwo.VWOClient;
1819
import com.vwo.enums.EventEnum;
1920
import com.vwo.models.user.VWOContext;
2021
import com.vwo.ServiceContainer;
2122
import com.vwo.models.request.EventArchPayload;
2223
import com.vwo.constants.Constants;
24+
import com.vwo.packages.logger.enums.LogLevelEnum;
25+
import com.vwo.packages.network_layer.manager.NetworkManager;
26+
import java.util.ArrayList;
2327
import java.util.HashMap;
24-
28+
import java.util.List;
2529
import java.util.Map;
2630

2731
public class ImpressionUtil {
2832

2933
/**
30-
* Creates and sends an impression for a variation shown event.
31-
* This function constructs the necessary properties and payload for the event
34+
* Sends an impression for a variation shown event.
35+
* This function constructs the necessary properties for the event
3236
* and uses the NetworkUtil to send a POST API request.
3337
*
3438
* @param serviceContainer The service container containing configuration.
3539
* @param campaignId The ID of the campaign.
3640
* @param variationId The ID of the variation shown to the user.
3741
* @param context The user context model containing user-specific data.
42+
* @param payload The payload data for tracking the user (created by caller).
3843
*/
39-
public static void createAndSendImpressionForVariationShown(
44+
public static void sendImpressionForVariationShown(
4045
ServiceContainer serviceContainer,
4146
int campaignId,
4247
int variationId,
43-
VWOContext context) {
48+
VWOContext context,
49+
EventArchPayload payload) {
4450
// Get base properties for the event
4551
Map<String, String> properties = NetworkUtil.getEventsBaseProperties(
4652
serviceContainer.getSettingsManager(),
4753
EventEnum.VWO_VARIATION_SHOWN.getValue(),
4854
encodeURIComponent(context.getUserAgent()),
4955
context.getIpAddress());
5056

51-
// Construct payload data for tracking the user
52-
EventArchPayload payload = NetworkUtil.getTrackUserPayloadData(
53-
serviceContainer,
54-
context.getId(),
55-
EventEnum.VWO_VARIATION_SHOWN.getValue(),
56-
campaignId,
57-
variationId,
58-
context.getUserAgent(),
59-
context.getIpAddress());
60-
6157
String campaignKeyWithFeatureName = CampaignUtil.getCampaignKeyFromCampaignId(serviceContainer.getSettings(), campaignId);
6258
String variationName = CampaignUtil.getVariationNameFromCampaignIdAndVariationId(serviceContainer.getSettings(), campaignId, variationId);
6359
String campaignType = CampaignUtil.getCampaignTypeFromCampaignId(serviceContainer.getSettings(), campaignId);
@@ -87,6 +83,63 @@ public static void createAndSendImpressionForVariationShown(
8783
}
8884
}
8985

86+
/**
87+
* Sends impressions for variation shown events in batch.
88+
* This function dispatches all collected payloads in a single network call.
89+
*
90+
* @param payloads List of EventArchPayload objects to send.
91+
* @param serviceContainer The service container containing configuration.
92+
*/
93+
public static void sendImpressionForVariationShownInBatch(
94+
List<EventArchPayload> payloads,
95+
ServiceContainer serviceContainer) {
96+
if (payloads == null || payloads.isEmpty()) {
97+
return;
98+
}
99+
100+
// Check if batch event queue is available
101+
if (serviceContainer.getBatchEventQueue() != null) {
102+
// Enqueue each payload to the batch queue for future processing
103+
for (EventArchPayload payload : payloads) {
104+
serviceContainer.getBatchEventQueue().enqueue(payload);
105+
}
106+
} else {
107+
// Convert payloads to list of maps for batch request
108+
List<Map<String, Object>> payloadMaps = new ArrayList<>();
109+
for (EventArchPayload payload : payloads) {
110+
Map<String, Object> payloadMap = VWOClient.objectMapper.convertValue(payload, Map.class);
111+
payloadMap = NetworkUtil.removeNullValues(payloadMap);
112+
payloadMaps.add(payloadMap);
113+
}
114+
115+
final int eventCount = payloadMaps.size();
116+
// Send all events in a single batch request asynchronously
117+
NetworkManager.getInstance().getExecutorService().submit(() -> {
118+
try {
119+
NetworkUtil.sendPostBatchRequest(
120+
serviceContainer.getSettingsManager(),
121+
payloadMaps,
122+
serviceContainer.getSettingsManager().accountId,
123+
serviceContainer.getSettingsManager().sdkKey,
124+
null
125+
);
126+
serviceContainer.getLoggerService().log(LogLevelEnum.DEBUG, "BATCH_IMPRESSION_SUCCESS", new HashMap<String, Object>() {
127+
{
128+
put("eventCount", String.valueOf(eventCount));
129+
put("accountId", serviceContainer.getSettingsManager().accountId.toString());
130+
}
131+
});
132+
} catch (Exception e) {
133+
serviceContainer.getLoggerService().log(LogLevelEnum.ERROR, "BATCH_IMPRESSION_FAILED", new HashMap<String, Object>() {
134+
{
135+
put("err", e.getMessage());
136+
}
137+
});
138+
}
139+
});
140+
}
141+
}
142+
90143
/**
91144
* Encodes the query parameters to ensure they are URL-safe
92145
*

src/main/java/com/vwo/utils/NetworkUtil.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -417,7 +417,7 @@ public static Boolean sendPostBatchRequest(SettingsManager settingsManager, Obje
417417

418418
String endpoint = UrlEnum.BATCH_EVENTS.getUrl();
419419
if (settingsManager.collectionPrefix != null && !settingsManager.collectionPrefix.isEmpty()) {
420-
endpoint = "/" + settingsManager.collectionPrefix + "/" + endpoint;
420+
endpoint = "/" + settingsManager.collectionPrefix + endpoint;
421421
}
422422

423423
// Create the request model
@@ -457,7 +457,7 @@ public static void sendEventDirectlyToDacdn(SettingsManager settingsManager, Map
457457

458458
String endpoint = UrlEnum.EVENTS.getUrl();
459459
if (settingsManager.collectionPrefix != null && !settingsManager.collectionPrefix.isEmpty()) {
460-
endpoint = "/" + settingsManager.collectionPrefix + "/" + endpoint;
460+
endpoint = "/" + settingsManager.collectionPrefix + endpoint;
461461
}
462462

463463
RequestModel request = new RequestModel(settingsManager.hostname, "POST", endpoint, properties, payload, headers, settingsManager.protocol, settingsManager.port);

src/main/java/com/vwo/utils/RuleEvaluationUtil.java

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,20 +15,20 @@
1515
*/
1616
package com.vwo.utils;
1717

18+
import com.vwo.enums.EventEnum;
1819
import com.vwo.models.Campaign;
1920
import com.vwo.models.Feature;
2021
import com.vwo.models.Variation;
22+
import com.vwo.models.request.EventArchPayload;
2123
import com.vwo.models.user.VWOContext;
2224
import com.vwo.packages.logger.enums.LogLevelEnum;
23-
import com.vwo.services.LoggerService;
2425
import com.vwo.services.StorageService;
2526
import com.vwo.ServiceContainer;
2627
import java.util.HashMap;
2728
import java.util.Map;
2829

2930

3031
import static com.vwo.utils.DecisionUtil.checkWhitelistingAndPreSeg;
31-
import static com.vwo.utils.ImpressionUtil.createAndSendImpressionForVariationShown;
3232

3333
public class RuleEvaluationUtil {
3434

@@ -70,20 +70,24 @@ public static Map<String, Object> evaluateRule(
7070
// Extract the results of the evaluation
7171
boolean preSegmentationResult = (Boolean) checkResult.get("preSegmentationResult");
7272
Variation whitelistedObject = (Variation) checkResult.get("whitelistedObject");
73+
EventArchPayload payload = null;
7374

74-
// If pre-segmentation is successful and a whitelisted object exists, proceed to send an impression
75+
// If pre-segmentation is successful and a whitelisted object exists, create payload for impression
7576
if (preSegmentationResult && whitelistedObject != null && whitelistedObject.getId() != null) {
7677
// Update the decision object with campaign and variation details
7778
decision.put("experimentId", campaign.getId());
7879
decision.put("experimentKey", campaign.getKey());
7980
decision.put("experimentVariationId", whitelistedObject.getId());
8081

81-
// Send an impression for the variation shown
82-
createAndSendImpressionForVariationShown(
82+
// Create payload for the variation shown (to be sent in batch by caller)
83+
payload = NetworkUtil.getTrackUserPayloadData(
8384
serviceContainer,
85+
context.getId(),
86+
EventEnum.VWO_VARIATION_SHOWN.getValue(),
8487
campaign.getId(),
8588
whitelistedObject.getId(),
86-
context
89+
context.getUserAgent(),
90+
context.getIpAddress()
8791
);
8892
}
8993

@@ -92,6 +96,7 @@ public static Map<String, Object> evaluateRule(
9296
result.put("preSegmentationResult", preSegmentationResult);
9397
result.put("whitelistedObject", whitelistedObject);
9498
result.put("updatedDecision", decision);
99+
result.put("payload", payload);
95100
return result;
96101
} catch (Exception exception) {
97102
serviceContainer.getLoggerService().log(LogLevelEnum.ERROR, "ERROR_EVALUATING_RULE", new HashMap<String, Object>() {

0 commit comments

Comments
 (0)