-
Notifications
You must be signed in to change notification settings - Fork 26
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(ups): Added UserProfileService Support. #326
Conversation
@@ -114,6 +119,11 @@ func (c *OptlyCache) UpdateConfigs(sdkKey string) { | |||
} | |||
} | |||
|
|||
// SetUserProfileService sets maps userProfileService against the given sdkKey | |||
func (c *OptlyCache) SetUserProfileService(sdkKey, userProfileService string) { |
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.
Please add lock 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.
we are using userProfileServiceMap cmap.ConcurrentMap
here which handles concurrency itself.
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 think we should still add the mutex here. For reading the ConcurrentMap should be fine but when setting, I believe the mutex should be used to ensure no race condition, even with the ConcurrentMap, just to be safe.. I could be wrong though.
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 need to check this part again.
Hey team, this is amazing news and something my team was considering contributing! 💟 Just a question, in the example config we see there is an option to specify custom URL for User Profile Service. However, reviewing PR I could not see an implementation for that. Is there a plan to support that any time soon? Even the PR description does not cover this. Thank you :-) |
Hi @anvth , We have updated the PR description, please have a look and let us know if there's anything else we can help with, Thanks. |
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! Just the one comment around mutex as @msohailhussain suggested.
Hey team, this is amazing news for us as we were planning to implement this. 😸 thank you 😸 |
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 have a suggestion and a clarification.
Other than those, it looks great to me as far as I can follow with go :)
case "lifo": | ||
u.lifoOrderedProfiles = append(u.lifoOrderedProfiles, profile.ID) | ||
default: | ||
// fifo by default | ||
u.fifoOrderedProfiles <- profile.ID |
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.
The benefit of supporting fifo/lifo is not clear to me. Can we just implement the default LRU cache unless we see clear reasons they prefer fifo/lifo?
// Lookup is used to retrieve past bucketing decisions for users | ||
func (r *RestUserProfileService) Lookup(userID string) (profile decision.UserProfile) { | ||
if userID == "" { | ||
return |
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.
Is it ok not returning profile in go?
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.
yes, if we don't do it here, it will just return empty profile struct.
lookupMethod, saveMethod, userIDKey
hello! 😸 all of the updates to rest UPS look wonderful 😻 all configurable via config.yml file 😻 great work! |
Hi @204Constie , In case of |
😸 this is perfectly fine, i was just wondering why is this the case. is the response used in any way ? is there some other reason ? i'm simply curious |
So for |
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.
please address
.travis.yml
Outdated
@@ -8,6 +8,9 @@ env: | |||
# may also want to run `go mod edit -go=1.13` to fix go.mod as well | |||
- GIMME_GO_VERSION=1.13.x GIMME_OS=linux GIMME_ARCH=amd64 | |||
|
|||
services: |
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.
what's the purpose of this service, please add as a comment.
@@ -95,6 +95,7 @@ Below is a comprehensive list of available configuration properties. | |||
|client.pollingInterval|OPTIMIZELY_CLIENT_POLLINGINTERVAL|The time between successive polls for updated project configuration. Default: 1m| | |||
|client.queueSize|OPTIMIZELY_CLIENT_QUEUESIZE|The max number of events pending dispatch. Default: 1000| | |||
|client.sdkKeyRegex|OPTIMIZELY_CLIENT_SDKKEYREGEX|Regex to validate SDK keys provided in request header. Default: ^\\w+(:\\w+)?$| | |||
|client.userProfileService|OPTIMIZELY_CLIENT_USERPROFILESERVICE| Property used to enable and set UserProfileServices. Default: ./config.yaml| |
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 think this needs to be revised.
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.
cmd/optimizely/main.go
Outdated
@@ -84,6 +87,11 @@ func loadConfig(v *viper.Viper) *config.AgentConfig { | |||
conf.Server.Interceptors = interceptors | |||
} | |||
|
|||
// Check if JSON string was set using OPTIMIZELY_CLIENT_USERPROFILESERVICE |
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.
OPTIMIZELY_CLIENT_USERPROFILESERVICE
what does it mean?
@@ -78,6 +78,10 @@ func NewDefaultConfig() *AgentConfig { | |||
EventURL: "https://logx.optimizely.com/v1/events", | |||
// https://github.com/google/re2/wiki/Syntax | |||
SdkKeyRegex: "^\\w+(:\\w+)?$", | |||
UserProfileService: UserProfileServiceConfigs{ |
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.
space before curly braces.
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.
Suggested by linter, Golang follows the same convention for initialising new properties.
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= | ||
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= | ||
github.com/onsi/gomega v1.16.0 h1:6gjqkI8iiRHMvdccRJM8rVKjCWk6ZIm6FTm3ddIe4/c= | ||
github.com/onsi/gomega v1.16.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= |
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.
so many packages are added, we will need to check if we can ship without redis.
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.
Have updated the PR description with build size with redis.
pkg/optimizely/cache.go
Outdated
|
||
createAndReturnUPSWithName := func(upsName string) decision.UserProfileService { | ||
if clientConfigUPSMap, ok := conf.UserProfileService["services"].(map[string]interface{}); ok { | ||
if defaultUserProfileServiceMap, ok := clientConfigUPSMap[upsName].(map[string]interface{}); ok { |
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.
userprofileserviceconfig should be better name.
pkg/optimizely/cache.go
Outdated
return nil | ||
} | ||
|
||
// Check if ups type was provided in the request headers |
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.
name
pkg/optimizely/cache.go
Outdated
// Check if ups type was provided in the request headers | ||
if ups, ok := userProfileServiceMap.Get(sdkKey); ok { | ||
if upsNameStr, ok := ups.(string); ok && upsNameStr != "" { | ||
return createAndReturnUPSWithName(upsNameStr) |
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.
initialize or New
@@ -25,6 +25,7 @@ import ( | |||
type Cache interface { | |||
GetClient(sdkKey string) (*OptlyClient, error) | |||
UpdateConfigs(sdkKey string) | |||
SetUserProfileService(sdkKey, userProfileService string) |
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.
add description, why we need to add here.
fifoOrderedProfiles chan string | ||
lifoOrderedProfiles []string | ||
lock sync.RWMutex | ||
isReady bool |
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 we need isReady?
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.
since we need to initialize fifoOrderedProfiles
with capacity
and this can only be done when first save is called, isReady
is used to this purpose. All other required initialisations also take place if isReady
is false.
u.fifoOrderedProfiles = make(chan string, u.Capacity)
7c0a1a0
to
7abddfc
Compare
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
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
in-memory
,rest
andredis
.lifo
andfifo
ordering inin-memory
UPS.Build Size
Build Time
Performance
UserProfileService EndPoints
POST /v1/lookup
:POST /v1/save
:Out of Box UserProfileService Usage
UserProfileService
, update theconfig.yaml
as shown below:UserProfileService
, update theconfig.yaml
as shown below:UserProfileService
, update theconfig.yaml
as shown below:Implement 2
POST
api's/lookup_endpoint
and/save_endpoint
on yourhost
. Api methods will bePOST
by default but can be updated throughlookupMethod
andsaveMethod
properties. Similarly, request parameter key foruser_id
can also be updated usinguserIDKey
property.lookup_endpoint
should acceptuser_id
in its json body or query (depending upon the method type) and if successful, return the status code200
with json response (keep in mind that when sending response,user_id
should be substituted with value ofuserIDKey
from config.yaml):save_endpoint
should accept the following parameters in its json body or query (depending upon the method type) and return the status code200
if successful (keep in mind thatuser_id
should be substituted with the value ofuserIDKey
from config.yaml):Custom UserProfileService Implementation
To implement a custom user profile service, followings steps need to be taken:
decision.UserProfileService
interface inplugins/userprofileservice/services
.init
method inside your UserProfileService file as shown below:config.yaml
file with yourUserProfileService
config as shown below:UserProfileServices
and wants to override thedefault
UserProfileService
for a specifcsdkKey
, they can do so by providing theUserProfileService
name in the request HeaderX-Optimizely-UPS-Name
.Tests