-
Notifications
You must be signed in to change notification settings - Fork 31
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
feat: Integrated ODPManager with UserContext and Optimizely client #490
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I reviewed the high level first and will look at more details again. The builder pattern for ODP configuration looks good. See comments for some changes suggested.
core-api/src/main/java/com/optimizely/ab/OptimizelyUserContext.java
Outdated
Show resolved
Hide resolved
core-api/src/main/java/com/optimizely/ab/OptimizelyUserContext.java
Outdated
Show resolved
Hide resolved
core-api/src/main/java/com/optimizely/ab/odp/ODPSegmentManager.java
Outdated
Show resolved
Hide resolved
core-api/src/main/java/com/optimizely/ab/odp/ODPSegmentManager.java
Outdated
Show resolved
Hide resolved
core-api/src/main/java/com/optimizely/ab/odp/ODPSegmentManager.java
Outdated
Show resolved
Hide resolved
@@ -319,10 +322,16 @@ public static Optimizely newDefaultInstance(ProjectConfigManager configManager, | |||
.withNotificationCenter(notificationCenter) | |||
.build(); | |||
|
|||
ODPApiManager defaultODPApiManager = new DefaultODPApiManager(); | |||
ODPManager odpManager = ODPManager.builder() | |||
.withApiManager(defaultODPApiManager) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can't we let it use DefaultODPApiManager by default?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We cannot do that because DefaultODPApiManager implements http calls and is only available in the http-client
project. The Core SDK does not know about its implementation. This is exactly how we inject polling datafile manager and batch event processor.
core-httpclient-impl/src/main/java/com/optimizely/ab/odp/DefaultODPApiManager.java
Outdated
Show resolved
Hide resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
All changes look great! A few more changes suggested.
I also want to discuss a couple of possible extensions to support Android after wrapping up this PR.
core-api/src/main/java/com/optimizely/ab/OptimizelyUserContext.java
Outdated
Show resolved
Hide resolved
ODPManager odpManager = optimizely.getODPManager(); | ||
if (odpManager != null) { | ||
List<ODPSegmentOption> segmentOptions = new ArrayList<>(); | ||
if (ignoreCache) { | ||
segmentOptions.add(ODPSegmentOption.IGNORE_CACHE); | ||
} | ||
if (resetCache) { | ||
segmentOptions.add(ODPSegmentOption.RESET_CACHE); | ||
synchronized (odpManager) { | ||
setQualifiedSegments(odpManager.getSegmentManager().getQualifiedSegments(userId, segmentOptions)); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ODPManager odpManager = optimizely.getODPManager(); | |
if (odpManager != null) { | |
List<ODPSegmentOption> segmentOptions = new ArrayList<>(); | |
if (ignoreCache) { | |
segmentOptions.add(ODPSegmentOption.IGNORE_CACHE); | |
} | |
if (resetCache) { | |
segmentOptions.add(ODPSegmentOption.RESET_CACHE); | |
synchronized (odpManager) { | |
setQualifiedSegments(odpManager.getSegmentManager().getQualifiedSegments(userId, segmentOptions)); | |
} | |
setQualifiedSegments(optimizely.fetchQualifiedSegments(userId)) |
This is the only place we have dependency on ODPManager in OptimizelyUserContext. Can we move this logic to Optimizely too?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I will do this but i just wanted to avoid method drilling. I do not want us to end up creating multiple levels of simple wrappers that do not add any value in between. This is why i preferred exposing ODPEventManager and ODPSegmentManager directly as getters. One option is to add wrappers for each ODP related function in the Optimizely
client. Another option is to let userContext use ODPManager directly so that we do not need to keep adding wrappers as we add functionality to ODPManager. This wrapper mechanism gets specially tricky when we provide multiple overrides of methods. I would suggest to keep it like this and let userContext access ODPManager through a getter in optimizely client and call its methods direclty.. what do you say?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@zashraf1985 it looks like a style preference with tradeoffs. I'll leave it to you.
core-api/src/main/java/com/optimizely/ab/OptimizelyUserContext.java
Outdated
Show resolved
Hide resolved
|
||
String query = String.format("query($userId: String, $audiences: [String]) {customer(%s: $userId) {audiences(subset: $audiences) {edges {node {name state}}}}}", userKey); | ||
String variables = String.format("{\"userId\": \"%s\", \"audiences\": [%s]}", userValue, segmentsString); | ||
String requestPayload = String.format("{\"query\": \"%s\", \"variables\": %s}", query, variables); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks like no gain at all in Java with this new format. I guess you're trying to avoid use JSON parser here :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There is still a gain. We are now using graphQL json format which takes variables separately which means if someone wants to inject anything in the query, it wont work because we are not concatenating variables directly in the query, we are passing them as variables.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Async solution looks good to me. A few more small changes suggested.
ODPManager odpManager = getODPManager(); | ||
if (odpManager != null) { | ||
odpManager.getEventManager().identifyUser(userId); | ||
} | ||
} | ||
|
||
public void identifyUser(@Nullable String vuid, String userId) { | ||
public void identifyUser(@Nullable String vuid, @Nullable String userId) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wondering if need this api here. If we can determine if userId is vuid or fsUserId, we do not need this call here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
@@ -299,21 +300,16 @@ public void setQualifiedSegments(List<String> qualifiedSegments) { | |||
|
|||
/** | |||
* Fetch all qualified segments for the user context. | |||
* | |||
* <p> | |||
* The segments fetched will be saved and can be accessed at any time by calling {@link #getQualifiedSegments()}. | |||
*/ | |||
public void fetchQualifiedSegments() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we return boolean (true/false for success/failure) for blocking fetchSegment
calls. Clients can check the status before continuing with decide
call?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
ODPManager odpManager = optimizely.getODPManager(); | ||
if (odpManager == null) { | ||
logger.error("Audience segments fetch failed (ODP is not enabled"); | ||
callback.invokeCallback(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can also pass the status boolean (success/failure) in the callback parameter.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
logger.error("Audience segments fetch failed (ODP is not enabled"); | ||
callback.invokeCallback(); | ||
} else { | ||
odpManager.getSegmentManager().getQualifiedSegments(userId, segments -> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If we want to keep this direct call to odpManager, I suggest to make "optimizely.identifyUser" call also direct call (for consistency). I still prefer moving all to optimizely :) I'm concerned about the increasing complexity of UserContext.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Removed the direct call and made a wrapper in optimizely
setQualifiedSegments(segments); | ||
} | ||
callback.invokeCallback(); | ||
}, segmentOptions); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This async solution looks good!
|
||
@FunctionalInterface | ||
public interface ODPSegmentCallback { | ||
void invokeCallback(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This name sounds redundant. What about onCompleted
or onFetchComplemeted
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
changed it to onCompleted
|
||
@FunctionalInterface | ||
public interface ODPSegmentCallback { | ||
void invokeCallback(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
void invokeCallback(); | |
void onCompleted(bool status); |
Integer getMaxSize(); | ||
Integer getCacheTimeoutSeconds(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why adding these to Cache interface? We do not care about these when clients implement their own cache with own size and timeout.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I needed these for some unit tests. I cant figure out any other way to verify some tests
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I removed these methods and tests checking for these values.
@@ -106,15 +106,66 @@ public List<String> getQualifiedSegments(ODPUserKey userKey, String userValue, L | |||
return qualifiedSegments; | |||
} | |||
|
|||
public void getQualifiedSegments(ODPUserKey userKey, String userValue, ODPSegmentFetchCallback callback, List<ODPSegmentOption> options) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM!
Summary
This PR integrates ODP functionality with UserContext and Optimizely client to make it available for users. There are two ways to integrate ODPManager.
Test plan
Issues
FSSDK-8389, FS-8683