Skip to content

Refact: Added builder pattern in decision listener #275

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
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 16 additions & 22 deletions core-api/src/main/java/com/optimizely/ab/Optimizely.java
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@
import com.optimizely.ab.event.internal.BuildVersionInfo;
import com.optimizely.ab.event.internal.EventFactory;
import com.optimizely.ab.event.internal.payload.EventBatch.ClientEngine;
import com.optimizely.ab.notification.DecisionInfoEnums;
import com.optimizely.ab.notification.DecisionNotification;
import com.optimizely.ab.notification.NotificationCenter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
Expand Down Expand Up @@ -653,7 +653,7 @@ <T extends Object> T getFeatureVariableValueForType(@Nonnull String featureKey,
Map<String, ?> copiedAttributes = copyAttributes(attributes);
FeatureDecision featureDecision = decisionService.getVariationForFeature(featureFlag, userId, copiedAttributes);
Boolean featureEnabled = false;
if (featureDecision.variation != null && featureDecision.variation.getFeatureEnabled()) {
if (featureDecision.variation != null) {
FeatureVariableUsageInstance featureVariableUsageInstance =
featureDecision.variation.getVariableIdToFeatureVariableUsageInstanceMap().get(variable.getId());
if (featureVariableUsageInstance != null) {
Expand All @@ -670,26 +670,20 @@ <T extends Object> T getFeatureVariableValueForType(@Nonnull String featureKey,
}

Object convertedValue = convertStringToType(variableValue, variableType);
Map<String, Object> decisionInfo = new HashMap<>();
decisionInfo.put(DecisionInfoEnums.FeatureVariableDecisionInfo.FEATURE_KEY.toString(), featureKey);
decisionInfo.put(DecisionInfoEnums.FeatureVariableDecisionInfo.FEATURE_ENABLED.toString(), featureEnabled);
decisionInfo.put(DecisionInfoEnums.FeatureVariableDecisionInfo.VARIABLE_KEY.toString(), variableKey);
decisionInfo.put(DecisionInfoEnums.FeatureVariableDecisionInfo.VARIABLE_TYPE.toString(), variableType);
decisionInfo.put(DecisionInfoEnums.FeatureVariableDecisionInfo.VARIABLE_VALUE.toString(), convertedValue);
if (featureDecision.decisionSource != null && featureDecision.decisionSource.equals(FeatureDecision.DecisionSource.EXPERIMENT)) {
decisionInfo.put(DecisionInfoEnums.FeatureVariableDecisionInfo.SOURCE_EXPERIMENT_KEY.toString(), featureDecision.experiment.getKey());
decisionInfo.put(DecisionInfoEnums.FeatureVariableDecisionInfo.SOURCE_VARIATION_KEY.toString(), featureDecision.variation.getKey());
decisionInfo.put(DecisionInfoEnums.FeatureVariableDecisionInfo.SOURCE.toString(), featureDecision.decisionSource);
} else {
decisionInfo.put(DecisionInfoEnums.FeatureVariableDecisionInfo.SOURCE_EXPERIMENT_KEY.toString(), null);
decisionInfo.put(DecisionInfoEnums.FeatureVariableDecisionInfo.SOURCE_VARIATION_KEY.toString(), null);
decisionInfo.put(DecisionInfoEnums.FeatureVariableDecisionInfo.SOURCE.toString(), FeatureDecision.DecisionSource.ROLLOUT);
}
notificationCenter.sendNotifications(NotificationCenter.NotificationType.Decision,
NotificationCenter.DecisionNotificationType.FEATURE_VARIABLE.toString(),
userId,
copiedAttributes,
decisionInfo);

DecisionNotification decisionNotification = DecisionNotification.newFeatureVariableBuilder()
.withUserId(userId)
.withAttributes(copiedAttributes)
.withFeatureKey(featureKey)
.withFeatureEnabled(featureEnabled)
.withVariableKey(variableKey)
.withVariableType(variableType)
.withVariableValue(convertedValue)
.withFeatureDecision(featureDecision)
.build();


notificationCenter.sendNotifications(decisionNotification);

return (T) convertedValue;
}
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
/****************************************************************************
* Copyright 2019, Optimizely, Inc. and contributors *
* *
* 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 *
* *
* http://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.notification;


import com.optimizely.ab.bucketing.FeatureDecision;
import com.optimizely.ab.config.FeatureVariable;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.HashMap;
import java.util.Map;

public class DecisionNotification {
protected String type;
protected String userId;
protected Map<String, ?> attributes;
protected Map<String, ?> decisionInfo;

protected DecisionNotification() {
}

protected DecisionNotification(@Nonnull String type,
@Nonnull String userId,
@Nullable Map<String, ?> attributes,
@Nonnull Map<String, ?> decisionInfo) {
this.type = type;
this.userId = userId;
if (attributes == null) {
attributes = new HashMap<>();
}
this.attributes = attributes;
this.decisionInfo = decisionInfo;
Copy link
Contributor

Choose a reason for hiding this comment

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

Are all of these fields allowed to be null? I see in BaseDecisionNotificationBuilder, that we default attributes to empty map -- that should be moved here (otherwise assert non-null in constructor) to make it consistent, otherwise nulls can slip in.

}

public String getType() {
return type;
}

public String getUserId() {
return userId;
}

public Map<String, ?> getAttributes() {
return attributes;
}

public Map<String, ?> getDecisionInfo() {
return decisionInfo;
}

public static FeatureVariableDecisionNotificationBuilder newFeatureVariableBuilder() {
return new FeatureVariableDecisionNotificationBuilder();
}

public static class FeatureVariableDecisionNotificationBuilder {

public static final String FEATURE_KEY = "feature_key";
public static final String FEATURE_ENABLED = "feature_enabled";
public static final String SOURCE = "source";
public static final String SOURCE_EXPERIMENT_KEY = "source_experiment_key";
public static final String SOURCE_VARIATION_KEY = "source_variation_key";
public static final String VARIABLE_KEY = "variable_key";
public static final String VARIABLE_TYPE = "variable_type";
public static final String VARIABLE_VALUE = "variable_value";

private String featureKey;
private Boolean featureEnabled;
private FeatureDecision featureDecision;
private String variableKey;
private FeatureVariable.VariableType variableType;
private Object variableValue;
private String userId;
private Map<String, ?> attributes;
private Map<String, Object> decisionInfo;

protected FeatureVariableDecisionNotificationBuilder() {
}

public FeatureVariableDecisionNotificationBuilder withUserId(String userId) {
this.userId = userId;
return this;
}

public FeatureVariableDecisionNotificationBuilder withAttributes(Map<String, ?> attributes) {
this.attributes = attributes;
return this;
}

public FeatureVariableDecisionNotificationBuilder withFeatureKey(String featureKey) {
this.featureKey = featureKey;
return this;
}

public FeatureVariableDecisionNotificationBuilder withFeatureEnabled(boolean featureEnabled) {
this.featureEnabled = featureEnabled;
return this;
}

public FeatureVariableDecisionNotificationBuilder withFeatureDecision(FeatureDecision featureDecision) {
this.featureDecision = featureDecision;
return this;
}

public FeatureVariableDecisionNotificationBuilder withVariableKey(String variableKey) {
this.variableKey = variableKey;
return this;
}

public FeatureVariableDecisionNotificationBuilder withVariableType(FeatureVariable.VariableType variableType) {
this.variableType = variableType;
return this;
}

public FeatureVariableDecisionNotificationBuilder withVariableValue(Object variableValue) {
this.variableValue = variableValue;
return this;
}

public DecisionNotification build() {
decisionInfo = new HashMap<>();
decisionInfo.put(FEATURE_KEY, featureKey);
decisionInfo.put(FEATURE_ENABLED, featureEnabled);
decisionInfo.put(VARIABLE_KEY, variableKey);
decisionInfo.put(VARIABLE_TYPE, variableType);
decisionInfo.put(VARIABLE_VALUE, variableValue);
if (featureDecision != null && featureDecision.decisionSource != null && FeatureDecision.DecisionSource.EXPERIMENT.equals(featureDecision.decisionSource)) {
decisionInfo.put(SOURCE_EXPERIMENT_KEY, featureDecision.experiment.getKey());
decisionInfo.put(SOURCE_VARIATION_KEY, featureDecision.variation.getKey());
decisionInfo.put(SOURCE, featureDecision.decisionSource);
} else {
decisionInfo.put(SOURCE_EXPERIMENT_KEY, null);
decisionInfo.put(SOURCE_VARIATION_KEY, null);
decisionInfo.put(SOURCE, FeatureDecision.DecisionSource.ROLLOUT);
}

return new DecisionNotification(
NotificationCenter.DecisionNotificationType.FEATURE_VARIABLE.toString(),
userId,
attributes,
decisionInfo);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,37 +17,17 @@
package com.optimizely.ab.notification;

import javax.annotation.Nonnull;
import java.util.HashMap;
import java.util.Map;

public abstract class DecisionNotificationListener implements NotificationListener, DecisionNotificationListenerInterface {
public interface DecisionNotificationListener {

/**
* Base notify called with var args. This method parses the parameters and calls the abstract method.
* onDecision called when an activate was triggered
*
* @param args - variable argument list based on the type of notification.
* @param decisionNotification - The decision notification object containing:
* type - The notification type.
* userId - The userId passed to the API.
* attributes - The attribute map passed to the API.
* decisionInfo - The decision information containing all parameters passed in API.
*/
@Override
public final void notify(Object... args) {
assert (args[0] instanceof String);
String type = (String) args[0];
assert (args[1] instanceof String);
String userId = (String) args[1];
Map<String, ?> attributes = null;
if (args[2] != null) {
assert (args[2] instanceof java.util.Map);
attributes = (Map<String, ?>) args[2];
} else {
attributes = new HashMap<>();
}
assert (args[3] instanceof java.util.Map);
Map<String, ?> decisionInfo = (Map<String, ?>) args[3];
onDecision(type, userId, attributes, decisionInfo);
}

@Override
public abstract void onDecision(@Nonnull String type,
@Nonnull String userId,
@Nonnull Map<String, ?> attributes,
@Nonnull Map<String, ?> decisionInfo);
void onDecision(@Nonnull DecisionNotification decisionNotification);
Copy link
Contributor

Choose a reason for hiding this comment

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

I've been trying to see if we can use generics to maintain some higher order generality. Ideally NotificationLister originally would have been defined like:

public interface NotificationListener<T> {
    void notify(T notification);
}

but since it wasn't trying to think the best way forward.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, you are right we can use generics but I think than we have to refactor Track and Activate listeners as well. If I am understanding this correctly?

}

This file was deleted.

Loading