Created
October 5, 2017 19:56
-
-
Save efenderbosch/838257bbc3cbfd6926efef0f980f709e to your computer and use it in GitHub Desktop.
Feature Toggles w/ LaunchDarkly and Guice
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import com.google.inject.BindingAnnotation; | |
import java.lang.annotation.Retention; | |
import java.lang.annotation.Target; | |
import static java.lang.annotation.ElementType.PARAMETER; | |
import static java.lang.annotation.RetentionPolicy.RUNTIME; | |
@Target(PARAMETER) | |
@Retention(RUNTIME) | |
@BindingAnnotation | |
public @interface Disabled {} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import com.google.inject.BindingAnnotation; | |
import java.lang.annotation.Retention; | |
import java.lang.annotation.Target; | |
import static java.lang.annotation.ElementType.PARAMETER; | |
import static java.lang.annotation.RetentionPolicy.RUNTIME; | |
@Target(PARAMETER) | |
@Retention(RUNTIME) | |
@BindingAnnotation | |
public @interface Enabled {} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import java.lang.annotation.Retention; | |
import java.lang.annotation.Target; | |
import static java.lang.annotation.ElementType.TYPE; | |
import static java.lang.annotation.RetentionPolicy.RUNTIME; | |
@Target(TYPE) | |
@Retention(RUNTIME) | |
public @interface Feature { | |
String key(); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import com.google.inject.AbstractModule; | |
import com.google.inject.Guice; | |
import com.google.inject.Injector; | |
import org.junit.Test; | |
import javax.inject.Inject; | |
import static com.google.inject.Scopes.SINGLETON; | |
import static com.unific.aws.config.featureflag.FeatureFlipAnnotationTest.FeatureFlipTestModule.MOCK_FEATURE_FLIP; | |
import static org.hamcrest.CoreMatchers.is; | |
import static org.junit.Assert.assertThat; | |
import static org.mockito.Mockito.mock; | |
import static org.mockito.Mockito.when; | |
public class FeatureFlipAnnotationTest { | |
private static final Injector INJECTOR = Guice.createInjector(new FeatureFlipTestModule()); | |
private static final SomeFeature SOME_FEATURE = INJECTOR.getInstance(SomeFeature.class); | |
@Test | |
public void test_enabled() throws Exception { | |
when(MOCK_FEATURE_FLIP.isFeatureEnabled("test_key")).thenReturn(true); | |
assertThat(SOME_FEATURE.foo(), is("enabled")); | |
} | |
@Test | |
public void test_disabled() throws Exception { | |
when(MOCK_FEATURE_FLIP.isFeatureEnabled("test_key")).thenReturn(false); | |
assertThat(SOME_FEATURE.foo(), is("disabled")); | |
} | |
@Feature(key = "test_key") | |
public interface SomeFeature { | |
String foo(); | |
} | |
public static class EnabledImplementation implements SomeFeature { | |
@Override | |
public String foo() { | |
return "enabled"; | |
} | |
} | |
public static class DisabledImplementation implements SomeFeature { | |
@Override | |
public String foo() { | |
return "disabled"; | |
} | |
} | |
public static class SomeFeatureProvider extends FeatureFlipProxyProvider<SomeFeature> { | |
@Inject | |
public SomeFeatureProvider(FeatureFlip featureFlip, | |
@Enabled SomeFeature enabledImplementation, | |
@Disabled SomeFeature disabledImplementation) { | |
super(SomeFeature.class, featureFlip, enabledImplementation, disabledImplementation); | |
} | |
} | |
public static class FeatureFlipTestModule extends AbstractModule { | |
public static final FeatureFlip MOCK_FEATURE_FLIP = mock(FeatureFlip.class); | |
@Override | |
protected void configure() { | |
bind(FeatureFlip.class).toInstance(MOCK_FEATURE_FLIP); | |
bind(SomeFeature.class).annotatedWith(Enabled.class).to(EnabledImplementation.class).in(SINGLETON); | |
bind(SomeFeature.class).annotatedWith(Disabled.class).to(DisabledImplementation.class).in(SINGLETON); | |
bind(SomeFeature.class).toProvider(SomeFeatureProvider.class); | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import java.lang.reflect.Proxy; | |
import javax.inject.Provider; | |
public abstract class FeatureFlipProxyProvider<T> implements Provider<T> { | |
private final T proxy; | |
protected FeatureFlipProxyProvider(Class<T> type, | |
FeatureFlip featureFlip, | |
T enabledImplementation, | |
T disabledImplementation) { | |
Feature feature = type.getAnnotation(Feature.class); | |
if (feature == null) { | |
throw new IllegalArgumentException(type.getName() + " must be annotated with @Feature"); | |
} | |
String key = feature.key(); | |
ClassLoader classLoader = FeatureFlipProxyProvider.class.getClassLoader(); | |
Class<?>[] interfaces = {type}; | |
proxy = (T) Proxy.newProxyInstance(classLoader, interfaces, | |
(proxy1, method, args) -> featureFlip.isFeatureEnabled(key) ? | |
method.invoke(enabledImplementation, args) : | |
method.invoke(disabledImplementation, args)); | |
} | |
@Override | |
public T get() { | |
return proxy; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment