Skip to content

feat: Added caching for OptimizelyConfig object #352

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 30 commits into from
Jan 9, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
99f03ac
Added Optimizely Config
Arshadf Dec 5, 2019
86cba8b
nit: changes of file structure
Arshadf Dec 5, 2019
211b370
nit: add comments
Arshadf Dec 6, 2019
a2801b7
Added Unit Test
Arshadf Dec 6, 2019
56335c3
Did some refactoring
zashraf1985 Dec 6, 2019
aebb8ca
Some more cleanup
zashraf1985 Dec 8, 2019
846abb7
Changed the datafile to use existing one
Arshadf Dec 9, 2019
d0e6776
Refactored some code to remove unnecassary changes
Arshadf Dec 9, 2019
4949cc7
Changes after review
Arshadf Dec 10, 2019
f0c4af9
Changes after review
Arshadf Dec 11, 2019
f00fced
Reverted Optimizely Tests
Arshadf Dec 16, 2019
108b7fb
Reverted Optimizely Tests
Arshadf Dec 16, 2019
fcc2263
Caching the optimizely config until revision is changed and added com…
Arshadf Dec 17, 2019
ac370fb
Restructure some code
Arshadf Dec 18, 2019
f56dbb3
Updated code after suggested breakdown of PR's
Arshadf Dec 19, 2019
f1c3d30
Removed integration in client and rebased from master
Arshadf Dec 19, 2019
b923b32
removed unused imports
zashraf1985 Dec 19, 2019
94217d5
Added tests for model classes and some refactoring
Arshadf Dec 23, 2019
f358b8a
Removed unused imports
Arshadf Dec 24, 2019
1fbd89b
Final Version
Arshadf Dec 27, 2019
1ab189e
Added caching
Arshadf Dec 20, 2019
a8fc2ba
Added Unit Test for caching
Arshadf Dec 30, 2019
0e0fe4f
nit updated headers with year 2020
Arshadf Jan 1, 2020
ce9d305
Finalized the caching tests
Arshadf Jan 2, 2020
57794a8
Updated header
Arshadf Jan 3, 2020
ab1f69c
Added a few comments
zashraf1985 Jan 3, 2020
59d3d42
removed unused imports
zashraf1985 Jan 3, 2020
4acfcda
reverted some imports
zashraf1985 Jan 3, 2020
2e8688e
Changes upon Mike's comments
Arshadf Jan 7, 2020
27aaf4b
nit
Arshadf Jan 8, 2020
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
48 changes: 33 additions & 15 deletions core-api/src/main/java/com/optimizely/ab/Optimizely.java
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
import com.optimizely.ab.event.internal.payload.EventBatch;
import com.optimizely.ab.notification.*;
import com.optimizely.ab.optimizelyconfig.OptimizelyConfig;
import com.optimizely.ab.optimizelyconfig.OptimizelyConfigManager;
import com.optimizely.ab.optimizelyconfig.OptimizelyConfigService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
Expand Down Expand Up @@ -87,6 +88,9 @@ public class Optimizely implements AutoCloseable {

private final ProjectConfigManager projectConfigManager;

@Nullable
private final OptimizelyConfigManager optimizelyConfigManager;

// TODO should be private
public final NotificationCenter notificationCenter;

Expand All @@ -99,6 +103,7 @@ private Optimizely(@Nonnull EventHandler eventHandler,
@Nonnull DecisionService decisionService,
@Nullable UserProfileService userProfileService,
@Nonnull ProjectConfigManager projectConfigManager,
@Nullable OptimizelyConfigManager optimizelyConfigManager,
@Nonnull NotificationCenter notificationCenter
) {
this.eventHandler = eventHandler;
Expand All @@ -107,6 +112,7 @@ private Optimizely(@Nonnull EventHandler eventHandler,
this.decisionService = decisionService;
this.userProfileService = userProfileService;
this.projectConfigManager = projectConfigManager;
this.optimizelyConfigManager = optimizelyConfigManager;
this.notificationCenter = notificationCenter;
}

Expand Down Expand Up @@ -884,20 +890,6 @@ public Variation getForcedVariation(@Nonnull String experimentKey,
return decisionService.getForcedVariation(experiment, userId);
}

/**
* Get {@link OptimizelyConfig} containing experiments and features map
*
* @return {@link OptimizelyConfig}
*/
public OptimizelyConfig getOptimizelyConfig() {
ProjectConfig projectConfig = getProjectConfig();
if (projectConfig == null) {
logger.error("Optimizely instance is not valid, failing getOptimizelyConfig call.");
return null;
}
return new OptimizelyConfigService(projectConfig).getConfig();
}

/**
* @return the current {@link ProjectConfig} instance.
*/
Expand Down Expand Up @@ -928,6 +920,25 @@ private boolean validateUserId(String userId) {
return true;
}

/**
* Get {@link OptimizelyConfig} containing experiments and features map
*
* @return {@link OptimizelyConfig}
*/
public OptimizelyConfig getOptimizelyConfig() {
ProjectConfig projectConfig = getProjectConfig();
if (projectConfig == null) {
logger.error("Optimizely instance is not valid, failing getOptimizelyConfig call.");
return null;
}
if (optimizelyConfigManager != null) {
return optimizelyConfigManager.getOptimizelyConfig();
}
// Generate and return a new OptimizelyConfig object as a fallback when consumer implements their own ProjectConfigManager without implementing OptimizelyConfigManager.
logger.debug("optimizelyConfigManager is null, generating new OptimizelyConfigObject as a fallback");
return new OptimizelyConfigService(projectConfig).getConfig();
}

/**
* Helper method which makes separate copy of attributesMap variable and returns it
*
Expand Down Expand Up @@ -1029,6 +1040,7 @@ public static class Builder {
private EventProcessor eventProcessor;
private ProjectConfig projectConfig;
private ProjectConfigManager projectConfigManager;
private OptimizelyConfigManager optimizelyConfigManager;
private UserProfileService userProfileService;
private NotificationCenter notificationCenter;

Expand Down Expand Up @@ -1155,6 +1167,12 @@ public Optimizely build() {
projectConfigManager = fallbackConfigManager;
}

// PollingProjectConfigManager now also implements OptimizelyConfigManager interface to support OptimizelyConfig API.
// This check is needed in case a consumer provides their own ProjectConfigManager which does nt implement OptimizelyConfigManager interface
if (projectConfigManager instanceof OptimizelyConfigManager) {
optimizelyConfigManager = (OptimizelyConfigManager) projectConfigManager;
}

if (notificationCenter == null) {
notificationCenter = new NotificationCenter();
}
Expand All @@ -1164,7 +1182,7 @@ public Optimizely build() {
eventProcessor = new ForwardingEventProcessor(eventHandler, notificationCenter);
}

return new Optimizely(eventHandler, eventProcessor, errorHandler, decisionService, userProfileService, projectConfigManager, notificationCenter);
return new Optimizely(eventHandler, eventProcessor, errorHandler, decisionService, userProfileService, projectConfigManager, optimizelyConfigManager, notificationCenter);
}
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/**
*
* Copyright 2019, Optimizely and contributors
* Copyright 2019-2020, Optimizely and contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand All @@ -18,6 +18,9 @@

import com.optimizely.ab.notification.NotificationCenter;
import com.optimizely.ab.notification.UpdateConfigNotification;
import com.optimizely.ab.optimizelyconfig.OptimizelyConfig;
import com.optimizely.ab.optimizelyconfig.OptimizelyConfigManager;
import com.optimizely.ab.optimizelyconfig.OptimizelyConfigService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

Expand All @@ -37,12 +40,13 @@
* is initially set. A default ProjectConfig can be provided to bootstrap the initial ProjectConfig
* return value and prevent blocking.
*/
public abstract class PollingProjectConfigManager implements ProjectConfigManager, AutoCloseable {
public abstract class PollingProjectConfigManager implements ProjectConfigManager, AutoCloseable, OptimizelyConfigManager {

private static final Logger logger = LoggerFactory.getLogger(PollingProjectConfigManager.class);
private static final UpdateConfigNotification SIGNAL = new UpdateConfigNotification();

private final AtomicReference<ProjectConfig> currentProjectConfig = new AtomicReference<>();
private final AtomicReference<OptimizelyConfig> currentOptimizelyConfig = new AtomicReference<>();
private final ScheduledExecutorService scheduledExecutorService;
private final long period;
private final TimeUnit timeUnit;
Expand Down Expand Up @@ -99,6 +103,7 @@ void setConfig(ProjectConfig projectConfig) {
logger.info("New datafile set with revision: {}. Old revision: {}", projectConfig.getRevision(), previousRevision);

currentProjectConfig.set(projectConfig);
currentOptimizelyConfig.set(new OptimizelyConfigService(projectConfig).getConfig());
countDownLatch.countDown();
notificationCenter.send(SIGNAL);
}
Expand Down Expand Up @@ -132,6 +137,15 @@ public ProjectConfig getConfig() {
return projectConfig == null ? currentProjectConfig.get() : projectConfig;
}

/**
* Returns the cached {@link OptimizelyConfig}
* @return {@link OptimizelyConfig}
*/
@Override
public OptimizelyConfig getOptimizelyConfig() {
return currentOptimizelyConfig.get();
}

public synchronized void start() {
if (started) {
logger.warn("Manager already started.");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,4 +45,21 @@ public Map<String, OptimizelyFeature> getFeaturesMap() {
public String getRevision() {
return revision;
}

@Override
public boolean equals(Object obj) {
if (obj == null || getClass() != obj.getClass()) return false;
if (obj == this) return true;
OptimizelyConfig optimizelyConfig = (OptimizelyConfig) obj;
return revision.equals(optimizelyConfig.getRevision()) &&
experimentsMap.equals(optimizelyConfig.getExperimentsMap()) &&
featuresMap.equals(optimizelyConfig.getFeaturesMap());
}

@Override
public int hashCode() {
int hash = revision.hashCode();
hash = 31 * hash + experimentsMap.hashCode();
return hash;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/**
*
* Copyright 2020, Optimizely 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.optimizelyconfig;

public interface OptimizelyConfigManager {
/**
* Implementations of this method should return {@link OptimizelyConfig}
*
* @return {@link OptimizelyConfig}
*/
OptimizelyConfig getOptimizelyConfig();
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/**
*
* Copyright 2019, Optimizely and contributors
* Copyright 2019-2020, Optimizely and contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -150,6 +150,26 @@ public ProjectConfig poll() {
assertEquals(newerProjectConfig, testProjectConfigManager.getConfig());
}

@Test
public void testSetOptimizelyConfig(){
assertNull(testProjectConfigManager.getOptimizelyConfig());

testProjectConfigManager.setConfig(projectConfig);
assertEquals("1480511547", testProjectConfigManager.getOptimizelyConfig().getRevision());

// cached config because project config is null
testProjectConfigManager.setConfig(null);
assertEquals("1480511547", testProjectConfigManager.getOptimizelyConfig().getRevision());

// created config with new revision
ProjectConfig newerProjectConfig = mock(ProjectConfig.class);
when(newerProjectConfig.getRevision()).thenReturn("new");

// verify the new optimizely config
testProjectConfigManager.setConfig(newerProjectConfig);
assertEquals("new", testProjectConfigManager.getOptimizelyConfig().getRevision());
}

@Test
public void testErroringProjectConfigManagerWithTimeout() throws Exception {
testProjectConfigManager = new TestProjectConfigManager() {
Expand Down