Skip to content

Commit 0ce5c75

Browse files
authored
Merge pull request thombergs#119 from thombergs/feature-flag-patterns
Feature flag patterns
2 parents a09f5e7 + d715f36 commit 0ce5c75

File tree

24 files changed

+389
-0
lines changed

24 files changed

+389
-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: 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+
import java.util.function.Supplier;
8+
9+
public class FeatureFlagFactoryBean<T> implements FactoryBean<T> {
10+
11+
private final Class<T> targetClass;
12+
private final Supplier<Boolean> featureFlagEvaluation;
13+
private final T beanWhenTrue;
14+
private final T beanWhenFalse;
15+
16+
public FeatureFlagFactoryBean(Class<T> targetClass, Supplier<Boolean> featureFlagEvaluation, T beanWhenTrue, T beanWhenFalse) {
17+
this.targetClass = targetClass;
18+
this.featureFlagEvaluation = featureFlagEvaluation;
19+
this.beanWhenTrue = beanWhenTrue;
20+
this.beanWhenFalse = beanWhenFalse;
21+
}
22+
23+
@Override
24+
public T getObject() {
25+
InvocationHandler invocationHandler = (proxy, method, args) -> {
26+
if (featureFlagEvaluation.get()) {
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: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
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 FeatureFlagFactoryBean<Service> {
8+
9+
public FeatureFlaggedService(FeatureFlagService featureFlagService) {
10+
super(
11+
Service.class,
12+
featureFlagService::isNewServiceEnabled,
13+
new NewService(),
14+
new OldService());
15+
}
16+
}
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+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
package io.reflectoring.featureflags.patterns.replacemethod;
2+
3+
import io.reflectoring.featureflags.FeatureFlagService;
4+
import org.springframework.context.annotation.Primary;
5+
import org.springframework.stereotype.Component;
6+
7+
@Component("replaceMethodFeatureFlaggedService")
8+
@Primary
9+
class FeatureFlaggedService implements Service {
10+
11+
private final FeatureFlagService featureFlagService;
12+
private final NewService newService;
13+
private final OldService oldService;
14+
15+
public FeatureFlaggedService(FeatureFlagService featureFlagService, NewService newService, OldService oldService) {
16+
this.featureFlagService = featureFlagService;
17+
this.newService = newService;
18+
this.oldService = oldService;
19+
}
20+
21+
@Override
22+
public int doSomething() {
23+
if (featureFlagService.isNewServiceEnabled()) {
24+
return newService.doSomething();
25+
} else {
26+
return oldService.doSomething();
27+
}
28+
}
29+
30+
}

0 commit comments

Comments
 (0)