diff --git a/providers/flagd/README.md b/providers/flagd/README.md index 4b645fb83..603cda1d1 100644 --- a/providers/flagd/README.md +++ b/providers/flagd/README.md @@ -64,9 +64,28 @@ Provider will attempt to detect file changes using polling. Polling happens at 5 second intervals and this is currently unconfigurable. This mode is useful for local development, tests and offline applications. +#### Custom Connector + +You can include a custom connector as a configuration option to customize how the in-process resolver fetches flags. +The custom connector must implement the [Connector interface](https://github.com/open-feature/java-sdk-contrib/blob/main/providers/flagd/src/main/java/dev/openfeature/contrib/providers/flagd/resolver/process/storage/connector/Connector.java). + +```java +Connector myCustomConnector = new MyCustomConnector(); +FlagdOptions options = + FlagdOptions.builder() + .resolverType(Config.Resolver.IN_PROCESS) + .customConnector(myCustomConnector) + .build(); + +FlagdProvider flagdProvider = new FlagdProvider(options); +``` + > [!IMPORTANT] -> Note that you can only use a single flag source (either gRPC or offline file) for the in-process resolver. -> If both sources are configured, offline mode will be selected. +> Note that the in-process resolver can only use a single flag source. +> If multiple sources are configured then only one would be selected based on the following order of preference: +> 1. Custom Connector +> 2. Offline file +> 3. gRPC ### Configuration options diff --git a/providers/flagd/src/main/java/dev/openfeature/contrib/providers/flagd/FlagdOptions.java b/providers/flagd/src/main/java/dev/openfeature/contrib/providers/flagd/FlagdOptions.java index 996de9d43..531c8921f 100644 --- a/providers/flagd/src/main/java/dev/openfeature/contrib/providers/flagd/FlagdOptions.java +++ b/providers/flagd/src/main/java/dev/openfeature/contrib/providers/flagd/FlagdOptions.java @@ -1,5 +1,6 @@ package dev.openfeature.contrib.providers.flagd; +import dev.openfeature.contrib.providers.flagd.resolver.process.storage.connector.Connector; import io.opentelemetry.api.GlobalOpenTelemetry; import io.opentelemetry.api.OpenTelemetry; import lombok.Builder; @@ -99,6 +100,11 @@ public class FlagdOptions { @Builder.Default private String offlineFlagSourcePath = fallBackToEnvOrDefault(Config.OFFLINE_SOURCE_PATH, null); + /** + * Inject a Custom Connector for fetching flags. + */ + private Connector customConnector; + /** * Inject OpenTelemetry for the library runtime. Providing sdk will initiate * distributed tracing for flagd grpc diff --git a/providers/flagd/src/main/java/dev/openfeature/contrib/providers/flagd/resolver/process/InProcessResolver.java b/providers/flagd/src/main/java/dev/openfeature/contrib/providers/flagd/resolver/process/InProcessResolver.java index e62a9fd49..e3a525e20 100644 --- a/providers/flagd/src/main/java/dev/openfeature/contrib/providers/flagd/resolver/process/InProcessResolver.java +++ b/providers/flagd/src/main/java/dev/openfeature/contrib/providers/flagd/resolver/process/InProcessResolver.java @@ -152,6 +152,9 @@ public ProviderEvaluation objectEvaluation(String key, Value defaultValue } static Connector getConnector(final FlagdOptions options) { + if (options.getCustomConnector() != null) { + return options.getCustomConnector(); + } return options.getOfflineFlagSourcePath() != null && !options.getOfflineFlagSourcePath().isEmpty() ? new FileConnector(options.getOfflineFlagSourcePath()) : new GrpcStreamConnector(options); diff --git a/providers/flagd/src/test/java/dev/openfeature/contrib/providers/flagd/FlagdOptionsTest.java b/providers/flagd/src/test/java/dev/openfeature/contrib/providers/flagd/FlagdOptionsTest.java index 53bcff503..dd0282258 100644 --- a/providers/flagd/src/test/java/dev/openfeature/contrib/providers/flagd/FlagdOptionsTest.java +++ b/providers/flagd/src/test/java/dev/openfeature/contrib/providers/flagd/FlagdOptionsTest.java @@ -1,5 +1,7 @@ package dev.openfeature.contrib.providers.flagd; +import dev.openfeature.contrib.providers.flagd.resolver.process.storage.MockConnector; +import dev.openfeature.contrib.providers.flagd.resolver.process.storage.connector.Connector; import io.opentelemetry.api.OpenTelemetry; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; @@ -28,6 +30,7 @@ void TestDefaults() { assertEquals(DEFAULT_MAX_EVENT_STREAM_RETRIES, builder.getMaxEventStreamRetries()); assertNull(builder.getSelector()); assertNull(builder.getOpenTelemetry()); + assertNull(builder.getCustomConnector()); assertNull(builder.getOfflineFlagSourcePath()); assertEquals(Resolver.RPC, builder.getResolverType()); } @@ -35,6 +38,7 @@ void TestDefaults() { @Test void TestBuilderOptions() { OpenTelemetry openTelemetry = Mockito.mock(OpenTelemetry.class); + Connector connector = new MockConnector(null); FlagdOptions flagdOptions = FlagdOptions.builder() .host("https://hosted-flagd") @@ -47,6 +51,7 @@ void TestBuilderOptions() { .selector("app=weatherApp") .offlineFlagSourcePath("some-path") .openTelemetry(openTelemetry) + .customConnector(connector) .resolverType(Resolver.IN_PROCESS) .build(); @@ -60,6 +65,7 @@ void TestBuilderOptions() { assertEquals("app=weatherApp", flagdOptions.getSelector()); assertEquals("some-path", flagdOptions.getOfflineFlagSourcePath()); assertEquals(openTelemetry, flagdOptions.getOpenTelemetry()); + assertEquals(connector, flagdOptions.getCustomConnector()); assertEquals(Resolver.IN_PROCESS, flagdOptions.getResolverType()); } diff --git a/providers/flagd/src/test/java/dev/openfeature/contrib/providers/flagd/resolver/process/InProcessResolverTest.java b/providers/flagd/src/test/java/dev/openfeature/contrib/providers/flagd/resolver/process/InProcessResolverTest.java index 81e41c8a0..d6a063c16 100644 --- a/providers/flagd/src/test/java/dev/openfeature/contrib/providers/flagd/resolver/process/InProcessResolverTest.java +++ b/providers/flagd/src/test/java/dev/openfeature/contrib/providers/flagd/resolver/process/InProcessResolverTest.java @@ -3,6 +3,7 @@ import dev.openfeature.contrib.providers.flagd.Config; import dev.openfeature.contrib.providers.flagd.FlagdOptions; import dev.openfeature.contrib.providers.flagd.resolver.process.model.FeatureFlag; +import dev.openfeature.contrib.providers.flagd.resolver.process.storage.MockConnector; import dev.openfeature.contrib.providers.flagd.resolver.process.storage.StorageState; import dev.openfeature.contrib.providers.flagd.resolver.process.storage.connector.file.FileConnector; import dev.openfeature.contrib.providers.flagd.resolver.process.storage.connector.grpc.GrpcStreamConnector; @@ -53,10 +54,13 @@ public void connectorSetup(){ FlagdOptions.builder().resolverType(Config.Resolver.IN_PROCESS).host("localhost").port(8080).build(); FlagdOptions forOfflineOptions = FlagdOptions.builder().resolverType(Config.Resolver.IN_PROCESS).offlineFlagSourcePath("path").build(); + FlagdOptions forCustomConnectorOptions = + FlagdOptions.builder().resolverType(Config.Resolver.IN_PROCESS).customConnector(new MockConnector(null)).build(); // then assertInstanceOf(GrpcStreamConnector.class, InProcessResolver.getConnector(forGrpcOptions)); assertInstanceOf(FileConnector.class, InProcessResolver.getConnector(forOfflineOptions)); + assertInstanceOf(MockConnector.class, InProcessResolver.getConnector(forCustomConnectorOptions)); } @Test diff --git a/providers/flagd/src/test/java/dev/openfeature/contrib/providers/flagd/resolver/process/storage/MockConnector.java b/providers/flagd/src/test/java/dev/openfeature/contrib/providers/flagd/resolver/process/storage/MockConnector.java index c0c288f8b..495b69778 100644 --- a/providers/flagd/src/test/java/dev/openfeature/contrib/providers/flagd/resolver/process/storage/MockConnector.java +++ b/providers/flagd/src/test/java/dev/openfeature/contrib/providers/flagd/resolver/process/storage/MockConnector.java @@ -12,7 +12,7 @@ public class MockConnector implements Connector { private BlockingQueue mockQueue; - MockConnector(final BlockingQueue mockQueue) { + public MockConnector(final BlockingQueue mockQueue) { this.mockQueue = mockQueue; }