Skip to content

feat: [flagd-in-process] Support Injection of a custom connector #900

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Aug 6, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 22 additions & 2 deletions providers/flagd/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,9 +64,29 @@ 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

If you want to extend In-process resolver to fetch flags from a different source, you can inject a custom connector to
the In-process resolver.
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

Expand Down
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,9 @@ public ProviderEvaluation<Value> 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);
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -28,13 +30,15 @@ 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());
}

@Test
void TestBuilderOptions() {
OpenTelemetry openTelemetry = Mockito.mock(OpenTelemetry.class);
Connector connector = new MockConnector(null);

FlagdOptions flagdOptions = FlagdOptions.builder()
.host("https://hosted-flagd")
Expand All @@ -47,6 +51,7 @@ void TestBuilderOptions() {
.selector("app=weatherApp")
.offlineFlagSourcePath("some-path")
.openTelemetry(openTelemetry)
.customConnector(connector)
.resolverType(Resolver.IN_PROCESS)
.build();

Expand All @@ -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());
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ public class MockConnector implements Connector {

private BlockingQueue<StreamPayload> mockQueue;

MockConnector(final BlockingQueue<StreamPayload> mockQueue) {
public MockConnector(final BlockingQueue<StreamPayload> mockQueue) {
this.mockQueue = mockQueue;
}

Expand Down