Skip to content

Commit cf86383

Browse files
committed
feature flag patterns
1 parent a09f5e7 commit cf86383

File tree

25 files changed

+380
-0
lines changed

25 files changed

+380
-0
lines changed

spring-boot/feature-flags/src/main/java/io/reflectoring/featureflags/FeatureFlagService.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,4 +22,6 @@ public interface FeatureFlagService {
2222
*/
2323
Boolean isUserActionTargetedFeatureActive();
2424

25+
Boolean isNewServiceEnabled();
26+
2527
}

spring-boot/feature-flags/src/main/java/io/reflectoring/featureflags/ff4j/FF4JFeatureFlagService.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,4 +33,9 @@ public Boolean isUserActionTargetedFeatureActive() {
3333
return null;
3434
}
3535

36+
@Override
37+
public Boolean isNewServiceEnabled() {
38+
return null;
39+
}
40+
3641
}

spring-boot/feature-flags/src/main/java/io/reflectoring/featureflags/launchdarkly/LaunchDarklyFeatureFlagService.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,13 @@
44
import com.launchdarkly.sdk.server.LDClient;
55
import io.reflectoring.featureflags.FeatureFlagService;
66
import io.reflectoring.featureflags.web.UserSession;
7+
import org.springframework.context.annotation.Primary;
78
import org.springframework.stereotype.Component;
89

910
import javax.annotation.PostConstruct;
1011

1112
@Component("launchdarkly")
13+
@Primary
1214
public class LaunchDarklyFeatureFlagService implements FeatureFlagService {
1315

1416
private final LDClient launchdarklyClient;
@@ -56,6 +58,11 @@ public Boolean isUserActionTargetedFeatureActive() {
5658
return launchdarklyClient.boolVariation("user-clicked-flag", getLaunchdarklyUserFromSession(), false);
5759
}
5860

61+
@Override
62+
public Boolean isNewServiceEnabled() {
63+
return true;
64+
}
65+
5966
private LDUser getLaunchdarklyUserFromSession() {
6067
return new LDUser.Builder(userSession.getUsername())
6168
.custom("clicked", userSession.hasClicked())
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package io.reflectoring.featureflags.patterns.ifelse;
2+
3+
import io.reflectoring.featureflags.FeatureFlagService;
4+
import org.springframework.stereotype.Component;
5+
6+
@Component
7+
class Service {
8+
9+
private final FeatureFlagService featureFlagService;
10+
11+
public Service(FeatureFlagService featureFlagService) {
12+
this.featureFlagService = featureFlagService;
13+
}
14+
15+
public int doSomething() {
16+
if (featureFlagService.isNewServiceEnabled()) {
17+
return 42;
18+
} else {
19+
return 1;
20+
}
21+
}
22+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
package io.reflectoring.featureflags.patterns.replacebean;
2+
3+
@FunctionalInterface
4+
public interface FeatureFlagEvaluation {
5+
6+
boolean evaluate();
7+
8+
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
package io.reflectoring.featureflags.patterns.replacebean;
2+
3+
import org.springframework.beans.factory.FactoryBean;
4+
5+
import java.lang.reflect.InvocationHandler;
6+
import java.lang.reflect.Proxy;
7+
8+
public class FeatureFlaggedBean<T> implements FactoryBean<T> {
9+
10+
private final Class<T> targetClass;
11+
private final FeatureFlagEvaluation featureFlagEvaluation;
12+
private final T beanWhenTrue;
13+
private final T beanWhenFalse;
14+
15+
public FeatureFlaggedBean(Class<T> targetClass, FeatureFlagEvaluation featureFlagEvaluation, T beanWhenTrue, T beanWhenFalse) {
16+
this.targetClass = targetClass;
17+
this.featureFlagEvaluation = featureFlagEvaluation;
18+
this.beanWhenTrue = beanWhenTrue;
19+
this.beanWhenFalse = beanWhenFalse;
20+
}
21+
22+
@Override
23+
public T getObject() {
24+
25+
InvocationHandler invocationHandler = (proxy, method, args) -> {
26+
if (featureFlagEvaluation.evaluate()) {
27+
return method.invoke(beanWhenTrue, args);
28+
} else {
29+
return method.invoke(beanWhenFalse, args);
30+
}
31+
};
32+
33+
Object proxy = Proxy.newProxyInstance(targetClass.getClassLoader(), new Class[]{targetClass}, invocationHandler);
34+
35+
return (T) proxy;
36+
}
37+
38+
@Override
39+
public Class<?> getObjectType() {
40+
return targetClass;
41+
}
42+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package io.reflectoring.featureflags.patterns.replacebean;
2+
3+
import io.reflectoring.featureflags.FeatureFlagService;
4+
import org.springframework.stereotype.Component;
5+
6+
@Component("replaceBeanFeatureFlaggedService")
7+
class FeatureFlaggedService extends FeatureFlaggedBean<Service> {
8+
9+
public FeatureFlaggedService(FeatureFlagService featureFlagService) {
10+
super(Service.class, featureFlagService::isNewServiceEnabled, new NewService(), new OldService());
11+
}
12+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
package io.reflectoring.featureflags.patterns.replacebean;
2+
3+
class NewService implements Service {
4+
@Override
5+
public int doSomething() {
6+
return 42;
7+
}
8+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
package io.reflectoring.featureflags.patterns.replacebean;
2+
3+
class OldService implements Service {
4+
@Override
5+
public int doSomething() {
6+
return 1;
7+
}
8+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package io.reflectoring.featureflags.patterns.replacebean;
2+
3+
interface Service {
4+
5+
int doSomething();
6+
7+
}

0 commit comments

Comments
 (0)