Skip to content
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(segment-manager): Adds implementation for ODPSegmentManager #353

Merged
merged 24 commits into from
Nov 16, 2022

Conversation

yasirfolio3
Copy link
Contributor

Summary

This PR adds support for ODPSegmentManager.

Test plan

  • Unit tests added

Issues

  • FSSDK-8511

Copy link

@jaeopt jaeopt left a comment

Choose a reason for hiding this comment

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

Looks great! A few changes suggested.

)

// ConfigState is used to represent state of odp
type ConfigState int64
Copy link

Choose a reason for hiding this comment

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

Is it common to use int64 for a few states? :)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

good catch 👍

s.lock.RLock()
defer s.lock.RUnlock()
value := true
if s.odpServiceIntegrated == NotIntegrated {
Copy link

Choose a reason for hiding this comment

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

In server-sdks, we can assume that events are sent only after datafile is parsed. So we do not need to support queueing if "NotDetermined" state (if it can make the implementation simpler).

package odp

// SegmentOption represents options controlling audience segments.
type SegmentOption int64
Copy link

Choose a reason for hiding this comment

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

Suggested change
type SegmentOption int64
type OptimizelySegmentOption int64

We'll open this to public. Clients will set options for fetchSegments public api.


const (
// IgnoreCache ignores cache (save/lookup)
IgnoreCache SegmentOption = iota
Copy link

Choose a reason for hiding this comment

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

Can we use this enum to public? Clients need to specify these options for fetchSegments public api.

Copy link
Contributor Author

@yasirfolio3 yasirfolio3 Nov 8, 2022

Choose a reason for hiding this comment

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

yep, anything that starts with a capital letter is public in golang.


// SegmentManager represents the odp segment manager.
type SegmentManager interface {
FetchQualifiedSegments(userKey, userValue string, options []SegmentOption) (segments []string, err error)
Copy link

Choose a reason for hiding this comment

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

Suggested change
FetchQualifiedSegments(userKey, userValue string, options []SegmentOption) (segments []string, err error)
FetchQualifiedSegments(userKey, userValue string, options []OptimizelySegmentOption) (segments []string, err error)

}

// NewSegmentManager creates and returns a new instance of DefaultSegmentManager.
func NewSegmentManager(cacheSize int, cacheTimeoutInSecs int64, odpConfig Config, apiManager SegmentAPIManager) *DefaultSegmentManager {
Copy link

Choose a reason for hiding this comment

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

We also need to support injection of custom SegmentCache (or they can specify size or timeout for default LRUCache). Server-sdks only.


// FetchQualifiedSegments fetches and returns qualified segments
func (s *DefaultSegmentManager) FetchQualifiedSegments(userKey, userValue string, options []SegmentOption) (segments []string, err error) {
if s.odpConfig == nil || s.odpConfig.GetAPIHost() == "" || s.odpConfig.GetAPIKey() == "" {
Copy link

Choose a reason for hiding this comment

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

Don't we use odpConfig.odpServiceIntegrated

Copy link
Contributor Author

@yasirfolio3 yasirfolio3 Nov 8, 2022

Choose a reason for hiding this comment

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

Makes sense, will update this.

apiKey: apiKey,
apiHost: apiHost,
segmentsToCheck: segmentsToCheck,
odpServiceIntegrated: NotDetermined, // initially queueing allowed until the first datafile is parsed
Copy link

Choose a reason for hiding this comment

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

Not sure if we have a use case for that, but - if initial apiKey and apiHost are valid, then odpServiceIntegrated is not consistent.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I have refactored it to not hold states, please have a look.

Copy link

@jaeopt jaeopt left a comment

Choose a reason for hiding this comment

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

LGTM with a nit

@@ -38,7 +38,7 @@ type OptimizelyUserContext struct {
}

// returns an instance of the optimizely user context.
func newOptimizelyUserContext(optimizely *OptimizelyClient, userID string, attributes map[string]interface{}, qualifiedSegments []string, forcedDecisionService *pkgDecision.ForcedDecisionService) OptimizelyUserContext {
func newOptimizelyUserContext(optimizely *OptimizelyClient, userID string, attributes map[string]interface{}, forcedDecisionService *pkgDecision.ForcedDecisionService, qualifiedSegments []string) OptimizelyUserContext {
Copy link

Choose a reason for hiding this comment

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

We need to differentiate cloning vs new context creation (so we can send IdenfityUser event only for the first creation not for cloing). We can keep it as is and fix it later when we work on the top-level integration.

Copy link
Contributor

Choose a reason for hiding this comment

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

what change you made for this?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Will be adding this in a separate PR which will be focused on integrating odpManager in userContext.

Comment on lines 44 to 48
segmentsCache: cache,
}
if segmentManager.segmentsCache == nil {
segmentManager.segmentsCache = NewLRUCache(cacheSize, cacheTimeoutInSecs)
}
Copy link

Choose a reason for hiding this comment

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

Suggested change
segmentsCache: cache,
}
if segmentManager.segmentsCache == nil {
segmentManager.segmentsCache = NewLRUCache(cacheSize, cacheTimeoutInSecs)
}
segmentsCache: cache ?? NewLRUCache(cacheSize, cacheTimeoutInSecs),
}

Copy link
Contributor Author

Choose a reason for hiding this comment

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

golang does not support ternary operators such as ?? or ?:.

Copy link
Contributor

@msohailhussain msohailhussain left a comment

Choose a reason for hiding this comment

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

please address my questions before merging. lgtm

@@ -38,7 +38,7 @@ type OptimizelyUserContext struct {
}

// returns an instance of the optimizely user context.
func newOptimizelyUserContext(optimizely *OptimizelyClient, userID string, attributes map[string]interface{}, qualifiedSegments []string, forcedDecisionService *pkgDecision.ForcedDecisionService) OptimizelyUserContext {
func newOptimizelyUserContext(optimizely *OptimizelyClient, userID string, attributes map[string]interface{}, forcedDecisionService *pkgDecision.ForcedDecisionService, qualifiedSegments []string) OptimizelyUserContext {
Copy link
Contributor

Choose a reason for hiding this comment

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

what change you made for this?

}

// DefaultConfig represents default implementation of odp config
type DefaultConfig struct {
Copy link
Contributor

Choose a reason for hiding this comment

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

i need to check name.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

couldn't name this Config as it was the name of the interface, also ODPConfig is not recommended by linter since the config resides under odp package. Any suggestions?

return s.apiHost
}

// GetSegmentsToCheck returns value for SegmentsToCheck.
Copy link
Contributor

Choose a reason for hiding this comment

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

can you explain bit more.


// SegmentAPIManager represents the segment API manager.
type SegmentAPIManager interface {
FetchSegments(apiKey, apiHost, userKey, userValue string, segmentsToCheck []string) ([]string, error)
Copy link
Contributor

Choose a reason for hiding this comment

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

just a thought, why we don't pass Config?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

good suggestion.

}

// FetchSegments returns qualified ODP segments
func (s *DefaultSegmentAPIManager) FetchSegments(apiKey, apiHost, userKey, userValue string, segmentsToCheck []string) ([]string, error) {
Copy link
Contributor

Choose a reason for hiding this comment

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

give more proper name instead of s


// Creates graphql query
func (s DefaultSegmentAPIManager) createRequestQuery(userKey, userValue string, segmentsToCheck []string) map[string]interface{} {
query := fmt.Sprintf(`query($userId: String, $audiences: [String]) {customer(%s: $userId) {audiences(subset: $audiences) {edges {node {name state}}}}}`, userKey)
Copy link
Contributor

Choose a reason for hiding this comment

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

you don't need fmt.Sprintf.
Please use multiline with indentation to have more visibility.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

String interpolation is only supported using fmt.Sprintf, will still look into it.

@msohailhussain msohailhussain merged commit c99e8dd into master Nov 16, 2022
@msohailhussain msohailhussain deleted the yasir/odp-segement-apimanager branch November 16, 2022 05:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants