Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
d9db9a5
WIP Initial SegmentManager commit
mikechu-optimizely Nov 4, 2022
e5fcd9a
WIP Initial commit fixes
mikechu-optimizely Nov 4, 2022
61d3e0f
WIP Initial commit fixes
mikechu-optimizely Nov 4, 2022
c4db70a
Finish OdpSegmentManager & interface
mikechu-optimizely Nov 8, 2022
b79861e
WIP unit tests starts
mikechu-optimizely Nov 8, 2022
bc60e1f
Unit tests & Segment Manager edits
mikechu-optimizely Nov 9, 2022
38281ab
Merge branch 'master' into mike/odp-segment-manager
mikechu-optimizely Nov 16, 2022
164b9d6
Merge branch 'master' into mike/odp-segment-manager
mikechu-optimizely Nov 18, 2022
4f62cd7
Fix merge issues; Add unit test
mikechu-optimizely Nov 18, 2022
a614e9f
Lint fixes
mikechu-optimizely Nov 18, 2022
a42ed21
Lint fixes?
mikechu-optimizely Nov 18, 2022
7efe971
Lint fixes??
mikechu-optimizely Nov 18, 2022
8b4f002
Lint fixes???
mikechu-optimizely Nov 18, 2022
3d27572
Remove re-added IOdpConfig.cs
mikechu-optimizely Nov 18, 2022
1c5a914
Add internal doc
mikechu-optimizely Nov 18, 2022
9d61e43
PR code review revisions
mikechu-optimizely Nov 22, 2022
40f2fdf
Update unit test
mikechu-optimizely Nov 22, 2022
3f91f81
Update OptimizelySDK/Odp/OdpSegmentManager.cs
mikechu-optimizely Nov 22, 2022
be49cb1
Pull request code revisions
mikechu-optimizely Nov 22, 2022
892cd3f
Remove time complexity looping/Linq
mikechu-optimizely Nov 23, 2022
77d56dd
Small refactor
mikechu-optimizely Nov 23, 2022
eedf603
Use OrderedDictionary
mikechu-optimizely Nov 23, 2022
ec81354
Use OrderedDictionary
mikechu-optimizely Nov 23, 2022
fe6a6d8
Merge remote-tracking branch 'origin/mike/odp-segment-manager' into m…
mikechu-optimizely Nov 23, 2022
File filter

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Finish OdpSegmentManager & interface
  • Loading branch information
mikechu-optimizely committed Nov 8, 2022
commit c4db70a72536f29ff81ebfc6b187b67379dfe72b
30 changes: 28 additions & 2 deletions OptimizelySDK/Odp/IOdpSegmentManager.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,35 @@
using System.Collections.Generic;
/*
* Copyright 2022 Optimizely
*
* 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
*
* https://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.
*/

using System.Collections.Generic;

namespace OptimizelySDK.Odp
{
/// <summary>
/// Interface to schedule connections to ODP for audience segmentation and caches the results.
/// </summary>
public interface IOdpSegmentManager
{
List<string> GetQualifiedSegments(string fsUserId, List<OdpSegmentOption> options = null);
/// <summary>
/// Attempts to fetch and return a list of a user's qualified segments from the local segments cache.
/// If no cached data exists for the target user, this fetches and caches data from the ODP server instead.
/// </summary>
/// <param name="fsUserId">The FS User ID identifying the user</param>
/// <param name="options">An array of OptimizelySegmentOption used to ignore and/or reset the cache.</param>
/// <returns>Qualified segments for the user from the cache or the ODP server if the cache is empty.</returns>
List<string> FetchQualifiedSegments(string fsUserId, List<OdpSegmentOption> options = null);
}
}
88 changes: 69 additions & 19 deletions OptimizelySDK/Odp/OdpSegmentManager.cs
Original file line number Diff line number Diff line change
@@ -1,19 +1,54 @@
using OptimizelySDK.Logger;
/*
* Copyright 2022 Optimizely
*
* 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
*
* https://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.
*/

using OptimizelySDK.Logger;
using System;
using System.Collections.Generic;
using System.Linq;

namespace OptimizelySDK.Odp
{
public class OdpSegmentManager: IOdpSegmentManager
/// <summary>
/// Concrete implementation that schedules connections to ODP for audience segmentation
/// and caches the results.
/// </summary>
public class OdpSegmentManager : IOdpSegmentManager
{
/// <summary>
/// Logger used to record messages that occur within the ODP client
/// </summary>
private readonly ILogger _logger;

/// <summary>
/// ODP segment API manager to communicate with ODP
/// </summary>
private readonly IOdpSegmentApiManager _apiManager;

/// <summary>
/// ODP configuration containing the connection parameters
/// </summary>
private readonly IOdpConfig _odpConfig;

private readonly Cache<List<string>> _segmentsCache;
/// <summary>
/// Cached segments
/// </summary>
private readonly LruCache<List<string>> _segmentsCache;

public OdpSegmentManager(IOdpConfig odpConfig, IOdpSegmentApiManager apiManager,
int cacheSize = Constants.DEFAULT_MAX_CACHE_SIZE, TimeSpan? itemTimeout = default,
int cacheSize = Constants.DEFAULT_MAX_CACHE_SIZE, TimeSpan? itemTimeout = null,
ILogger logger = null
)
{
Expand All @@ -32,7 +67,16 @@ public OdpSegmentManager(IOdpConfig odpConfig, IOdpSegmentApiManager apiManager,
_segmentsCache = new LruCache<List<string>>(cacheSize, timeout, logger);
}

public List<string> GetQualifiedSegments(string fsUserId, List<OdpSegmentOption> options = null)
/// <summary>
/// Attempts to fetch and return a list of a user's qualified segments from the local segments cache.
/// If no cached data exists for the target user, this fetches and caches data from the ODP server instead.
/// </summary>
/// <param name="fsUserId">The FS User ID identifying the user</param>
/// <param name="options">An array of OptimizelySegmentOption used to ignore and/or reset the cache.</param>
/// <returns>Qualified segments for the user from the cache or the ODP server if the cache is empty.</returns>
public List<string> FetchQualifiedSegments(string fsUserId,
List<OdpSegmentOption> options = null
)
{
if (!_odpConfig.IsReady())
{
Expand All @@ -46,45 +90,51 @@ public List<string> GetQualifiedSegments(string fsUserId, List<OdpSegmentOption>
"No Segments are used in the project, Not Fetching segments. Returning empty list");
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 please check with jae, Returning empty list may be revised.

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 do. @jaeopt ☝️

Copy link
Contributor Author

@mikechu-optimizely mikechu-optimizely Nov 22, 2022

Choose a reason for hiding this comment

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

I'm not completely sure how to read it, but it looks like the Swift implementation returns null

Java returns empty collection.

Python looks like it returns an empty array.

Go also seems to return an empty slice.


I do see I need a correction when ODP config is not ready. I should have returned null instead.

Copy link
Contributor

Choose a reason for hiding this comment

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

@mikechu-optimizely For any fetch error (odp not integrated, network errors, etc), we should return null. For empty segmentToCheck, we should return empty array.

return new List<string>();
}


options = options ?? new List<OdpSegmentOption>();

List<string> qualifiedSegments;
var cacheKey = GetCacheKey(OdpUserKeyType.FS_USER_ID.ToString().ToLower(), fsUserId);

if (options.Contains(OdpSegmentOption.ResetCache))
{
_segmentsCache.reset();
_segmentsCache.Reset();
}
else if (!options.Contains(OdpSegmentOption.IgnoreCache))
{
qualifiedSegments = _segmentsCache.lookup(cacheKey);
qualifiedSegments = _segmentsCache.Lookup(cacheKey);
if (qualifiedSegments != null)
{
_logger.Log(LogLevel.DEBUG,"ODP Cache Hit. Returning segments from Cache.");
_logger.Log(LogLevel.DEBUG, "ODP Cache Hit. Returning segments from Cache.");
return qualifiedSegments;
}
}

_logger.Log(LogLevel.DEBUG, "ODP Cache Miss. Making a call to ODP Server.");

var qualifiedSegmentsResponse = _apiManager.FetchSegments(
_odpConfig.ApiKey,
_odpConfig.ApiHost + Constants.ODP_GRAPHQL_API_ENDPOINT_PATH,
OdpUserKeyType.FS_USER_ID, fsUserId, _odpConfig.SegmentsToCheck);

var parser = ResponseJsonParserFactory.getParser();
qualifiedSegments = parser.parseQualifiedSegments(qualifiedSegmentsResponse);
qualifiedSegments = _apiManager.FetchSegments(
_odpConfig.ApiKey,
_odpConfig.ApiHost + Constants.ODP_GRAPHQL_API_ENDPOINT_PATH,
OdpUserKeyType.FS_USER_ID, fsUserId, _odpConfig.SegmentsToCheck)
.ToList();

if (qualifiedSegments != null && !options.Contains(OdpSegmentOption.IgnoreCache))
if (!options.Contains(OdpSegmentOption.IgnoreCache))
{
_segmentsCache.save(cacheKey, qualifiedSegments);
_segmentsCache.Save(cacheKey, qualifiedSegments);
}

return qualifiedSegments;
}

/// <summary>
/// Creates a key used to identify which user fetchQualifiedSegments should lookup and save to in the segments cache
/// </summary>
/// <param name="userKey">Always 'fs_user_id' (parameter for consistency with other SDKs)</param>
/// <param name="userValue">Arbitrary string representing the full stack user ID</param>
/// <returns>Concatenates inputs and returns the string "{userKey}-$-{userValue}"</returns>
private static string GetCacheKey(string userKey, string userValue)
{
return userKey + "-$-" + userValue;
return $"{userKey}-$-{userValue}";
}
}
}