Merge branch 'master' into http-clients
This commit is contained in:
@@ -21,10 +21,18 @@
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-jdbc</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-thymeleaf</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.h2database</groupId>
|
||||
<artifactId>h2</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- TOGGLZ -->
|
||||
<dependency>
|
||||
|
||||
@@ -22,4 +22,6 @@ public interface FeatureFlagService {
|
||||
*/
|
||||
Boolean isUserActionTargetedFeatureActive();
|
||||
|
||||
Boolean isNewServiceEnabled();
|
||||
|
||||
}
|
||||
|
||||
@@ -33,4 +33,9 @@ public class FF4JFeatureFlagService implements FeatureFlagService {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean isNewServiceEnabled() {
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
package io.reflectoring.featureflags.implementations;
|
||||
|
||||
public interface FeatureFlagService {
|
||||
|
||||
Boolean featureOne();
|
||||
|
||||
Integer featureTwo();
|
||||
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package io.reflectoring.featureflags.implementations.code;
|
||||
|
||||
import io.reflectoring.featureflags.implementations.FeatureFlagService;
|
||||
|
||||
public class CodeBackedFeatureFlagService implements FeatureFlagService {
|
||||
@Override
|
||||
public Boolean featureOne() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Integer featureTwo() {
|
||||
return 42;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
package io.reflectoring.featureflags.implementations.contextsensitive;
|
||||
|
||||
import io.reflectoring.featureflags.implementations.FeatureFlagService;
|
||||
import io.reflectoring.featureflags.implementations.contextsensitive.Feature.RolloutStrategy;
|
||||
import io.reflectoring.featureflags.web.UserSession;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
|
||||
public class ContextSensitiveFeatureFlagService implements FeatureFlagService {
|
||||
|
||||
private final JdbcTemplate jdbcTemplate;
|
||||
private final UserSession userSession;
|
||||
|
||||
public ContextSensitiveFeatureFlagService(JdbcTemplate jdbcTemplate, UserSession userSession) {
|
||||
this.jdbcTemplate = jdbcTemplate;
|
||||
this.userSession = userSession;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean featureOne() {
|
||||
Feature feature = getFeatureFromDatabase();
|
||||
if (feature == null) {
|
||||
return Boolean.FALSE;
|
||||
}
|
||||
return feature.evaluateBoolean(userSession.getUsername());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Integer featureTwo() {
|
||||
Feature feature = getFeatureFromDatabase();
|
||||
if (feature == null) {
|
||||
return null;
|
||||
}
|
||||
return feature.evaluateInt(userSession.getUsername());
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private Feature getFeatureFromDatabase() {
|
||||
return jdbcTemplate.query("select targeting, value, defaultValue, percentage from features where feature_key='FEATURE_ONE'", resultSet -> {
|
||||
if (!resultSet.next()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
RolloutStrategy rolloutStrategy = Enum.valueOf(RolloutStrategy.class, resultSet.getString(1));
|
||||
String value = resultSet.getString(2);
|
||||
String defaultValue = resultSet.getString(3);
|
||||
int percentage = resultSet.getInt(4);
|
||||
|
||||
return new Feature(rolloutStrategy, value, defaultValue, percentage);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
package io.reflectoring.featureflags.implementations.contextsensitive;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.Arrays;
|
||||
|
||||
public class Feature {
|
||||
|
||||
public enum RolloutStrategy {
|
||||
GLOBAL,
|
||||
PERCENTAGE;
|
||||
}
|
||||
|
||||
private final RolloutStrategy rolloutStrategy;
|
||||
|
||||
private final int percentage;
|
||||
private final String value;
|
||||
private final String defaultValue;
|
||||
|
||||
public Feature(RolloutStrategy rolloutStrategy, String value, String defaultValue, int percentage) {
|
||||
this.rolloutStrategy = rolloutStrategy;
|
||||
this.percentage = percentage;
|
||||
this.value = value;
|
||||
this.defaultValue = defaultValue;
|
||||
}
|
||||
|
||||
public boolean evaluateBoolean(String userId) {
|
||||
switch (this.rolloutStrategy) {
|
||||
case GLOBAL:
|
||||
return this.getBooleanValue();
|
||||
case PERCENTAGE:
|
||||
if (percentageHashCode(userId) <= this.percentage) {
|
||||
return this.getBooleanValue();
|
||||
} else {
|
||||
return this.getBooleanDefaultValue();
|
||||
}
|
||||
}
|
||||
|
||||
return this.getBooleanDefaultValue();
|
||||
}
|
||||
|
||||
public Integer evaluateInt(String userId) {
|
||||
switch (this.rolloutStrategy) {
|
||||
case GLOBAL:
|
||||
return this.getIntValue();
|
||||
case PERCENTAGE:
|
||||
if (percentageHashCode(userId) <= this.percentage) {
|
||||
return this.getIntValue();
|
||||
} else {
|
||||
return this.getIntDefaultValue();
|
||||
}
|
||||
}
|
||||
|
||||
return this.getIntDefaultValue();
|
||||
}
|
||||
|
||||
double percentageHashCode(String text) {
|
||||
try {
|
||||
MessageDigest digest = MessageDigest.getInstance("SHA-256");
|
||||
byte[] encodedhash = digest.digest(
|
||||
text.getBytes(StandardCharsets.UTF_8));
|
||||
double INTEGER_RANGE = 1L << 32;
|
||||
return (((long) Arrays.hashCode(encodedhash) - Integer.MIN_VALUE) / INTEGER_RANGE) * 100;
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public RolloutStrategy getTargeting() {
|
||||
return rolloutStrategy;
|
||||
}
|
||||
|
||||
public int getPercentage() {
|
||||
return percentage;
|
||||
}
|
||||
|
||||
public int getIntValue() {
|
||||
return Integer.parseInt(this.value);
|
||||
}
|
||||
|
||||
public int getIntDefaultValue() {
|
||||
return Integer.parseInt(this.defaultValue);
|
||||
}
|
||||
|
||||
|
||||
public boolean getBooleanValue() {
|
||||
return Boolean.parseBoolean(this.value);
|
||||
}
|
||||
|
||||
public boolean getBooleanDefaultValue() {
|
||||
return Boolean.parseBoolean(this.defaultValue);
|
||||
}
|
||||
|
||||
public String getStringValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
public String getDefaultValue() {
|
||||
return defaultValue;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package io.reflectoring.featureflags.implementations.database;
|
||||
|
||||
import io.reflectoring.featureflags.implementations.FeatureFlagService;
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
|
||||
public class DatabaseBackedFeatureFlagService implements FeatureFlagService {
|
||||
|
||||
private JdbcTemplate jdbcTemplate;
|
||||
|
||||
@Override
|
||||
public Boolean featureOne() {
|
||||
return jdbcTemplate.query("select value from features where feature_key='FEATURE_ONE'", resultSet -> {
|
||||
if(!resultSet.next()){
|
||||
return false;
|
||||
}
|
||||
|
||||
boolean value = Boolean.parseBoolean(resultSet.getString(1));
|
||||
return value ? Boolean.TRUE : Boolean.FALSE;
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public Integer featureTwo() {
|
||||
return jdbcTemplate.query("select value from features where feature_key='FEATURE_TWO'", resultSet -> {
|
||||
if(!resultSet.next()){
|
||||
return null;
|
||||
}
|
||||
|
||||
return Integer.valueOf(resultSet.getString(1));
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package io.reflectoring.featureflags.implementations.launchdarkly;
|
||||
|
||||
import com.launchdarkly.sdk.LDUser;
|
||||
import com.launchdarkly.sdk.server.LDClient;
|
||||
import io.reflectoring.featureflags.implementations.FeatureFlagService;
|
||||
import io.reflectoring.featureflags.web.UserSession;
|
||||
|
||||
public class LaunchDarklyFeatureFlagService implements FeatureFlagService {
|
||||
|
||||
private final LDClient launchdarklyClient;
|
||||
private final UserSession userSession;
|
||||
|
||||
public LaunchDarklyFeatureFlagService(LDClient launchdarklyClient, UserSession userSession) {
|
||||
this.launchdarklyClient = launchdarklyClient;
|
||||
this.userSession = userSession;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean featureOne() {
|
||||
return launchdarklyClient.boolVariation("feature-one", getLaunchdarklyUserFromSession(), false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Integer featureTwo() {
|
||||
return launchdarklyClient.intVariation("feature-two", getLaunchdarklyUserFromSession(), 0);
|
||||
}
|
||||
|
||||
private LDUser getLaunchdarklyUserFromSession() {
|
||||
return new LDUser.Builder(userSession.getUsername())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package io.reflectoring.featureflags.implementations.properties;
|
||||
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Component
|
||||
@ConfigurationProperties("features")
|
||||
public class FeatureProperties {
|
||||
|
||||
private boolean featureOne;
|
||||
private int featureTwo;
|
||||
|
||||
public FeatureProperties() {
|
||||
}
|
||||
|
||||
public boolean getFeatureOne() {
|
||||
return featureOne;
|
||||
}
|
||||
|
||||
public void setFeatureOne(boolean featureOne) {
|
||||
this.featureOne = featureOne;
|
||||
}
|
||||
|
||||
public int getFeatureTwo() {
|
||||
return featureTwo;
|
||||
}
|
||||
|
||||
public void setFeatureTwo(int featureTwo) {
|
||||
this.featureTwo = featureTwo;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package io.reflectoring.featureflags.implementations.properties;
|
||||
|
||||
|
||||
import io.reflectoring.featureflags.implementations.FeatureFlagService;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Component
|
||||
public class PropertiesBackedFeatureFlagService implements FeatureFlagService {
|
||||
|
||||
private final FeatureProperties featureProperties;
|
||||
|
||||
public PropertiesBackedFeatureFlagService(FeatureProperties featureProperties) {
|
||||
this.featureProperties = featureProperties;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean featureOne() {
|
||||
return featureProperties.getFeatureOne();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Integer featureTwo() {
|
||||
return featureProperties.getFeatureTwo();
|
||||
}
|
||||
}
|
||||
@@ -4,11 +4,13 @@ import com.launchdarkly.sdk.LDUser;
|
||||
import com.launchdarkly.sdk.server.LDClient;
|
||||
import io.reflectoring.featureflags.FeatureFlagService;
|
||||
import io.reflectoring.featureflags.web.UserSession;
|
||||
import org.springframework.context.annotation.Primary;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
|
||||
@Component("launchdarkly")
|
||||
@Primary
|
||||
public class LaunchDarklyFeatureFlagService implements FeatureFlagService {
|
||||
|
||||
private final LDClient launchdarklyClient;
|
||||
@@ -56,6 +58,11 @@ public class LaunchDarklyFeatureFlagService implements FeatureFlagService {
|
||||
return launchdarklyClient.boolVariation("user-clicked-flag", getLaunchdarklyUserFromSession(), false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean isNewServiceEnabled() {
|
||||
return true;
|
||||
}
|
||||
|
||||
private LDUser getLaunchdarklyUserFromSession() {
|
||||
return new LDUser.Builder(userSession.getUsername())
|
||||
.custom("clicked", userSession.hasClicked())
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
package io.reflectoring.featureflags.patterns.ifelse;
|
||||
|
||||
import io.reflectoring.featureflags.FeatureFlagService;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Component
|
||||
class Service {
|
||||
|
||||
private final FeatureFlagService featureFlagService;
|
||||
|
||||
public Service(FeatureFlagService featureFlagService) {
|
||||
this.featureFlagService = featureFlagService;
|
||||
}
|
||||
|
||||
public int doSomething() {
|
||||
if (featureFlagService.isNewServiceEnabled()) {
|
||||
return 42;
|
||||
} else {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
package io.reflectoring.featureflags.patterns.replacebean;
|
||||
|
||||
import org.springframework.beans.factory.FactoryBean;
|
||||
|
||||
import java.lang.reflect.InvocationHandler;
|
||||
import java.lang.reflect.Proxy;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
public class FeatureFlagFactoryBean<T> implements FactoryBean<T> {
|
||||
|
||||
private final Class<T> targetClass;
|
||||
private final Supplier<Boolean> featureFlagEvaluation;
|
||||
private final T beanWhenTrue;
|
||||
private final T beanWhenFalse;
|
||||
|
||||
public FeatureFlagFactoryBean(Class<T> targetClass, Supplier<Boolean> featureFlagEvaluation, T beanWhenTrue, T beanWhenFalse) {
|
||||
this.targetClass = targetClass;
|
||||
this.featureFlagEvaluation = featureFlagEvaluation;
|
||||
this.beanWhenTrue = beanWhenTrue;
|
||||
this.beanWhenFalse = beanWhenFalse;
|
||||
}
|
||||
|
||||
@Override
|
||||
public T getObject() {
|
||||
InvocationHandler invocationHandler = (proxy, method, args) -> {
|
||||
if (featureFlagEvaluation.get()) {
|
||||
return method.invoke(beanWhenTrue, args);
|
||||
} else {
|
||||
return method.invoke(beanWhenFalse, args);
|
||||
}
|
||||
};
|
||||
|
||||
Object proxy = Proxy.newProxyInstance(targetClass.getClassLoader(), new Class[]{targetClass}, invocationHandler);
|
||||
|
||||
return (T) proxy;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<?> getObjectType() {
|
||||
return targetClass;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package io.reflectoring.featureflags.patterns.replacebean;
|
||||
|
||||
import io.reflectoring.featureflags.FeatureFlagService;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Component("replaceBeanFeatureFlaggedService")
|
||||
class FeatureFlaggedService extends FeatureFlagFactoryBean<Service> {
|
||||
|
||||
public FeatureFlaggedService(FeatureFlagService featureFlagService) {
|
||||
super(
|
||||
Service.class,
|
||||
featureFlagService::isNewServiceEnabled,
|
||||
new NewService(),
|
||||
new OldService());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package io.reflectoring.featureflags.patterns.replacebean;
|
||||
|
||||
class NewService implements Service {
|
||||
@Override
|
||||
public int doSomething() {
|
||||
return 42;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package io.reflectoring.featureflags.patterns.replacebean;
|
||||
|
||||
class OldService implements Service {
|
||||
@Override
|
||||
public int doSomething() {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package io.reflectoring.featureflags.patterns.replacebean;
|
||||
|
||||
interface Service {
|
||||
|
||||
int doSomething();
|
||||
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package io.reflectoring.featureflags.patterns.replacemethod;
|
||||
|
||||
import io.reflectoring.featureflags.FeatureFlagService;
|
||||
import org.springframework.context.annotation.Primary;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Component("replaceMethodFeatureFlaggedService")
|
||||
@Primary
|
||||
class FeatureFlaggedService implements Service {
|
||||
|
||||
private final FeatureFlagService featureFlagService;
|
||||
private final NewService newService;
|
||||
private final OldService oldService;
|
||||
|
||||
public FeatureFlaggedService(FeatureFlagService featureFlagService, NewService newService, OldService oldService) {
|
||||
this.featureFlagService = featureFlagService;
|
||||
this.newService = newService;
|
||||
this.oldService = oldService;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int doSomething() {
|
||||
if (featureFlagService.isNewServiceEnabled()) {
|
||||
return newService.doSomething();
|
||||
} else {
|
||||
return oldService.doSomething();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package io.reflectoring.featureflags.patterns.replacemethod;
|
||||
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Component
|
||||
class NewService implements Service {
|
||||
@Override
|
||||
public int doSomething() {
|
||||
return 42;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package io.reflectoring.featureflags.patterns.replacemethod;
|
||||
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Component
|
||||
class OldService implements Service {
|
||||
@Override
|
||||
public int doSomething() {
|
||||
return 1;
|
||||
}
|
||||
|
||||
public int doSomethingElse(){
|
||||
return 2;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package io.reflectoring.featureflags.patterns.replacemethod;
|
||||
|
||||
interface Service {
|
||||
|
||||
int doSomething();
|
||||
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package io.reflectoring.featureflags.patterns.replacemodule;
|
||||
|
||||
import io.reflectoring.featureflags.FeatureFlagService;
|
||||
import io.reflectoring.featureflags.patterns.replacebean.FeatureFlagFactoryBean;
|
||||
import io.reflectoring.featureflags.patterns.replacemodule.newmodule.NewService1;
|
||||
import io.reflectoring.featureflags.patterns.replacemodule.newmodule.NewService2;
|
||||
import io.reflectoring.featureflags.patterns.replacemodule.oldmodule.OldService1;
|
||||
import io.reflectoring.featureflags.patterns.replacemodule.oldmodule.OldService2;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
@Configuration
|
||||
class FeatureFlaggedServiceModule {
|
||||
|
||||
private final FeatureFlagService featureFlagService;
|
||||
|
||||
public FeatureFlaggedServiceModule(FeatureFlagService featureFlagService) {
|
||||
this.featureFlagService = featureFlagService;
|
||||
}
|
||||
|
||||
@Bean("replaceModuleService1")
|
||||
FeatureFlagFactoryBean<Service1> service1() {
|
||||
return new FeatureFlagFactoryBean<>(Service1.class, featureFlagService::isNewServiceEnabled, new NewService1(), new OldService1());
|
||||
}
|
||||
|
||||
@Bean("replaceModuleService2")
|
||||
FeatureFlagFactoryBean<Service2> service2() {
|
||||
return new FeatureFlagFactoryBean<>(Service2.class, featureFlagService::isNewServiceEnabled, new NewService2(), new OldService2());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package io.reflectoring.featureflags.patterns.replacemodule;
|
||||
|
||||
public interface Service1 {
|
||||
|
||||
int doSomething();
|
||||
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package io.reflectoring.featureflags.patterns.replacemodule;
|
||||
|
||||
public interface Service2 {
|
||||
|
||||
int doSomethingElse();
|
||||
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package io.reflectoring.featureflags.patterns.replacemodule;
|
||||
|
||||
import io.reflectoring.featureflags.patterns.replacemodule.oldmodule.OldService1;
|
||||
import io.reflectoring.featureflags.patterns.replacemodule.oldmodule.OldService2;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
@Configuration
|
||||
class ServiceModule {
|
||||
|
||||
@Bean
|
||||
Service1 service1() {
|
||||
return new OldService1();
|
||||
}
|
||||
|
||||
@Bean
|
||||
Service2 service2() {
|
||||
return new OldService2();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package io.reflectoring.featureflags.patterns.replacemodule.newmodule;
|
||||
|
||||
import io.reflectoring.featureflags.patterns.replacemodule.Service1;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Component
|
||||
public class NewService1 implements Service1 {
|
||||
@Override
|
||||
public int doSomething() {
|
||||
return 42;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package io.reflectoring.featureflags.patterns.replacemodule.newmodule;
|
||||
|
||||
import io.reflectoring.featureflags.patterns.replacemodule.Service2;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Component
|
||||
public class NewService2 implements Service2 {
|
||||
|
||||
@Override
|
||||
public int doSomethingElse() {
|
||||
return 42;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package io.reflectoring.featureflags.patterns.replacemodule.oldmodule;
|
||||
|
||||
import io.reflectoring.featureflags.patterns.replacemodule.Service1;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Component
|
||||
public class OldService1 implements Service1 {
|
||||
@Override
|
||||
public int doSomething() {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package io.reflectoring.featureflags.patterns.replacemodule.oldmodule;
|
||||
|
||||
import io.reflectoring.featureflags.patterns.replacemodule.Service2;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Component
|
||||
public class OldService2 implements Service2 {
|
||||
|
||||
@Override
|
||||
public int doSomethingElse() {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
@@ -26,4 +26,9 @@ public class TooglzFeatureFlagService implements FeatureFlagService {
|
||||
return Features.USER_ACTION_TARGETED_FEATURE.isActive();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean isNewServiceEnabled() {
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -15,4 +15,8 @@ togglz:
|
||||
enabled: true
|
||||
secured: false
|
||||
path: /togglz
|
||||
use-management-port: false
|
||||
use-management-port: false
|
||||
|
||||
features:
|
||||
featureOne: true
|
||||
featureTwo: 42
|
||||
@@ -0,0 +1,19 @@
|
||||
package io.reflectoring.featureflags.implementations.contextsensitive;
|
||||
|
||||
import io.reflectoring.featureflags.implementations.contextsensitive.Feature.RolloutStrategy;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.offset;
|
||||
|
||||
public class FeatureTest {
|
||||
|
||||
@Test
|
||||
void testHashCode(){
|
||||
Feature feature = new Feature(RolloutStrategy.PERCENTAGE, "true", "false", 50);
|
||||
assertThat(feature.percentageHashCode("1")).isCloseTo(27.74d, offset(0.01d));
|
||||
assertThat(feature.percentageHashCode("2")).isCloseTo(81.12d, offset(0.01d));
|
||||
assertThat(feature.percentageHashCode("3")).isCloseTo(21.69d, offset(0.01d));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
package io.reflectoring.featureflags.patterns.replacebean;
|
||||
|
||||
import io.reflectoring.featureflags.FeatureFlagService;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.Mockito;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.boot.test.mock.mockito.MockBean;
|
||||
|
||||
import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
|
||||
import static org.mockito.BDDMockito.given;
|
||||
|
||||
@SpringBootTest
|
||||
public class ReplaceBeanTest {
|
||||
|
||||
@MockBean
|
||||
private FeatureFlagService featureFlagService;
|
||||
|
||||
@Autowired
|
||||
private Service service;
|
||||
|
||||
@BeforeEach
|
||||
void resetMocks() {
|
||||
Mockito.reset(featureFlagService);
|
||||
}
|
||||
|
||||
@Test
|
||||
void oldServiceTest() {
|
||||
given(featureFlagService.isNewServiceEnabled()).willReturn(false);
|
||||
assertThat(service.doSomething()).isEqualTo(1);
|
||||
}
|
||||
|
||||
@Test
|
||||
void newServiceTest() {
|
||||
given(featureFlagService.isNewServiceEnabled()).willReturn(true);
|
||||
assertThat(service.doSomething()).isEqualTo(42);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
package io.reflectoring.featureflags.patterns.replacemethod;
|
||||
|
||||
import io.reflectoring.featureflags.FeatureFlagService;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.Mockito;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.boot.test.mock.mockito.MockBean;
|
||||
|
||||
import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
|
||||
import static org.mockito.BDDMockito.given;
|
||||
|
||||
@SpringBootTest
|
||||
public class ReplaceMethodTest {
|
||||
|
||||
@MockBean
|
||||
private FeatureFlagService featureFlagService;
|
||||
|
||||
@Autowired
|
||||
private Service service;
|
||||
|
||||
@Autowired
|
||||
private OldService oldService;
|
||||
|
||||
@BeforeEach
|
||||
void resetMocks() {
|
||||
Mockito.reset(featureFlagService);
|
||||
}
|
||||
|
||||
@Test
|
||||
void oldServiceTest() {
|
||||
given(featureFlagService.isNewServiceEnabled()).willReturn(false);
|
||||
assertThat(service.doSomething()).isEqualTo(1);
|
||||
assertThat(oldService.doSomethingElse()).isEqualTo(2);
|
||||
}
|
||||
|
||||
@Test
|
||||
void newServiceTest() {
|
||||
given(featureFlagService.isNewServiceEnabled()).willReturn(true);
|
||||
assertThat(service.doSomething()).isEqualTo(42);
|
||||
// doSomethingElse() is not behind a feature flag, so it should return the same value independant of the feature flag
|
||||
assertThat(oldService.doSomethingElse()).isEqualTo(2);
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user