Skip to content
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

feat: flagd in-process provider #412

Merged
merged 32 commits into from
Sep 7, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
6b398d8
refactor packaging and initializing helpers
Kavindu-Dodan Aug 30, 2023
43b8d49
simple flag parsing
Kavindu-Dodan Aug 31, 2023
b834106
minimal end to end solution
Kavindu-Dodan Aug 31, 2023
b7917de
first evaluation state
Kavindu-Dodan Aug 31, 2023
c8a3960
grpc handling and json logic
Kavindu-Dodan Sep 1, 2023
37b3df7
resolving logic cleanup
Kavindu-Dodan Sep 1, 2023
860c6ca
refactor storage layer
Kavindu-Dodan Sep 1, 2023
fc5f344
connector refactored
Kavindu-Dodan Sep 1, 2023
22246fe
doc comments and organize
Kavindu-Dodan Sep 4, 2023
e46c62c
event handling wiring
Kavindu-Dodan Sep 4, 2023
bd53b6b
resolving tests
Kavindu-Dodan Sep 4, 2023
2bcde9b
lint fixes
Kavindu-Dodan Sep 4, 2023
bda533f
review changes and fix lint
Kavindu-Dodan Sep 5, 2023
009ae86
schema validation, tests and lint
Kavindu-Dodan Sep 5, 2023
aa61605
spotbug fixes
Kavindu-Dodan Sep 5, 2023
a895402
fix submodule status
Kavindu-Dodan Sep 5, 2023
2b88c08
disable e2e testst till framework is ready
Kavindu-Dodan Sep 5, 2023
d7e0c54
fix exclusions
Kavindu-Dodan Sep 5, 2023
d3888ad
Update providers/flagd/src/main/java/dev/openfeature/contrib/provider…
Kavindu-Dodan Sep 6, 2023
da9e252
fix test harness and review changes
Kavindu-Dodan Sep 6, 2023
d74b3f9
fix object resolving
Kavindu-Dodan Sep 6, 2023
cc8bdc6
testing storage connector contract
Kavindu-Dodan Sep 6, 2023
2e4677c
tests for grpc connector
Kavindu-Dodan Sep 6, 2023
81fb334
documentation
Kavindu-Dodan Sep 6, 2023
96240a7
reset retry
Kavindu-Dodan Sep 6, 2023
63d9d0e
Update providers/flagd/src/test/java/dev/openfeature/contrib/provider…
Kavindu-Dodan Sep 7, 2023
e90a954
Update providers/flagd/src/test/java/dev/openfeature/contrib/provider…
Kavindu-Dodan Sep 7, 2023
a0915b0
Update providers/flagd/src/test/java/dev/openfeature/contrib/provider…
Kavindu-Dodan Sep 7, 2023
7ce88e6
Update providers/flagd/README.md
Kavindu-Dodan Sep 7, 2023
a24dfab
Update providers/flagd/src/test/java/dev/openfeature/contrib/provider…
Kavindu-Dodan Sep 7, 2023
c8ba6ee
add tests for int and double
Kavindu-Dodan Sep 7, 2023
729c1e6
make wait configurable
Kavindu-Dodan Sep 7, 2023
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
6 changes: 5 additions & 1 deletion providers/flagd/CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
# flagd Provider for OpenFeature

Given below is the architecture of the provider implementation.

![img.png](architecture.png)

## Building

`mvn compile` will pull the `schemas` submodule, and build the gRPC/protobug resources.
`mvn compile` will pull the `schemas` and `sync` submodule, and build the gRPC resources.
Note that in some editors, you will need to disable some automatic compilation options to prevent your editor from cleaning them.
In vscode for instance, the following settings are recommended:

Expand Down
54 changes: 38 additions & 16 deletions providers/flagd/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,13 @@ A feature flag daemon with a Unix philosophy.

## Usage

The `FlagdProvider` communicates with flagd via the gRPC protocol. Instantiate a new FlagdProvider instance, and
configure the OpenFeature SDK to use it:
### Remote resolver

This is the default mode of operation of the provider.
In this mode, `FlagdProvider` communicates with [flagd](https://github.com/open-feature/flagd) via the gRPC protocol.
Flag evaluations happens remotely at the connected flagd instance.

Instantiate a new FlagdProvider instance, and configure the OpenFeature SDK to use it:

```java
// Create a flagd instance with default options
Expand All @@ -27,26 +32,43 @@ FlagdProvider flagd = new FlagdProvider();
OpenFeatureAPI.getInstance().setProvider(flagd);
```

### In-process resolver

This mode perform flag evaluations locally(in-process). Flag configurations for evaluations are obtained via gRPC protocol using [sync protobuf schema](https://buf.build/open-feature/flagd/file/main:sync/v1/sync_service.proto) service definition.

Consider following example to create a `FlagdProvider` with in-process evaluations,

```java
FlagdProvider flagdProvider = new FlagdProvider(
FlagdOptions.builder()
.resolverType(Config.ResolverType.IN_PROCESS)
.build());
```

In the above example, in-process handlers attempt to connect to a sync service on address `localhost:8013` to obtain flag configurations

### Configuration options

Options can be defined in the constructor or as environment variables, with constructor options having the highest
precedence.

Default options can be overridden through a `FlagdOptions` based constructor or set to be picked up from the environment
variables.

### Supported environmental variables

| Option name | Environment variable name | Type | Default | Values |
|-----------------------|--------------------------------|---------|-----------|--------------|
| host | FLAGD_HOST | string | localhost | |
| port | FLAGD_PORT | number | 8013 | |
| tls | FLAGD_TLS | boolean | false | |
| socketPath | FLAGD_SOCKET_PATH | string | null | |
| certPath | FLAGD_SERVER_CERT_PATH | string | null | |
| cache | FLAGD_CACHE | string | lru | lru,disabled |
| maxCacheSize | FLAGD_MAX_CACHE_SIZE | int | 1000 | |
| maxEventStreamRetries | FLAGD_MAX_EVENT_STREAM_RETRIES | int | 5 | |
| retryBackoffMs | FLAGD_RETRY_BACKOFF_MS | int | 1000 | |
| deadline | FLAGD_DEADLINE_MS | int | 500 | |
Given below are the supported configurations. Note that some configurations are only applicable for remote resolver.

| Option name | Environment variable name | Type & Values | Default | Compatible resolver |
|-----------------------|--------------------------------|------------------------|-----------|---------------------|
| host | FLAGD_HOST | String | localhost | Remote & In-process |
| port | FLAGD_PORT | int | 8013 | Remote & In-process |
| tls | FLAGD_TLS | boolean | false | Remote & In-process |
| socketPath | FLAGD_SOCKET_PATH | String | null | Remote & In-process |
| certPath | FLAGD_SERVER_CERT_PATH | String | null | Remote & In-process |
| cache | FLAGD_CACHE | String - lru, disabled | lru | Remote |
| maxCacheSize | FLAGD_MAX_CACHE_SIZE | int | 1000 | Remote |
| maxEventStreamRetries | FLAGD_MAX_EVENT_STREAM_RETRIES | int | 5 | Remote |
| retryBackoffMs | FLAGD_RETRY_BACKOFF_MS | int | 1000 | Remote |
| deadline | FLAGD_DEADLINE_MS | int | 500 | Remote |

### OpenTelemetry support

Expand Down
Binary file added providers/flagd/architecture.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
158 changes: 106 additions & 52 deletions providers/flagd/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,30 @@
<version>1.57.2</version>
</dependency>

<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.15.2</version>
</dependency>

<dependency>
<groupId>io.github.jamsesso</groupId>
<artifactId>json-logic-java</artifactId>
<version>1.0.7</version>
</dependency>

<dependency>
<groupId>com.networknt</groupId>
<artifactId>json-schema-validator</artifactId>
<version>1.0.86</version>
<exclusions>
<exclusion>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</exclusion>
</exclusions>
</dependency>

<dependency>
<!-- necessary for Java 9+ -->
<groupId>org.apache.tomcat</groupId>
Expand Down Expand Up @@ -126,7 +150,7 @@
</configuration>
</execution>
<execution>
<id>copy-protobuf-definition</id>
<id>copy-schema-definition</id>
<phase>validate</phase>
<goals>
<goal>exec</goal>
Expand All @@ -140,6 +164,36 @@
</arguments>
</configuration>
</execution>
<execution>
<id>copy-sync-definition</id>
<phase>validate</phase>
<goals>
<goal>exec</goal>
</goals>
<configuration>
<!-- run: cp schemas/protobuf/sync/v1/sync_service.proto src/main/proto/ -->
<executable>cp</executable>
<arguments>
<argument>schemas/protobuf/sync/v1/sync_service.proto</argument>
<argument>src/main/proto/</argument>
</arguments>
</configuration>
</execution>
<execution>
<id>copy-schema-json</id>
<phase>validate</phase>
<goals>
<goal>exec</goal>
</goals>
<configuration>
<!-- run: cp schemas/json/flagd-definitions.json src/main/resources/ -->
<executable>cp</executable>
<arguments>
<argument>schemas/json/flagd-definitions.json</argument>
<argument>src/main/resources/</argument>
</arguments>
</configuration>
</execution>
</executions>
</plugin>

Expand All @@ -166,57 +220,57 @@

<profiles>
<profile>
<!-- this profile handles running the flagd e2e tests -->
<id>e2e</id>
<properties>
<!-- run the e2e tests by clearing the exclusions -->
<testExclusions/>
</properties>
<build>
<plugins>
<!-- pull the gherkin tests as a git submodule -->
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<version>3.1.0</version>
<executions>
<execution>
<id>update-test-harness-submodule</id>
<phase>validate</phase>
<goals>
<goal>exec</goal>
</goals>
<configuration>
<!-- run: git submodule update \-\-init \-\-recursive -->
<executable>git</executable>
<arguments>
<argument>submodule</argument>
<argument>update</argument>
<argument>--init</argument>
<argument>test-harness</argument>
</arguments>
</configuration>
</execution>
<execution>
<id>copy-gherkin-tests</id>
<phase>validate</phase>
<goals>
<goal>exec</goal>
</goals>
<configuration>
<!-- copy the feature spec we want to test into resources so them can be easily loaded -->
<!-- run: cp test-harness/features/evaluation.feature src/test/resources/features/ -->
<executable>cp</executable>
<arguments>
<argument>test-harness/features/evaluation.feature</argument>
<argument>src/test/resources/features/</argument>
</arguments>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
<!-- this profile handles running the flagd e2e tests -->
<id>e2e</id>
<properties>
<!-- run the e2e tests by clearing the exclusions -->
<testExclusions/>
</properties>
<build>
<plugins>
<!-- pull the gherkin tests as a git submodule -->
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<version>3.1.0</version>
<executions>
<execution>
<id>update-test-harness-submodule</id>
<phase>validate</phase>
<goals>
<goal>exec</goal>
</goals>
<configuration>
<!-- run: git submodule update \-\-init \-\-recursive -->
<executable>git</executable>
<arguments>
<argument>submodule</argument>
<argument>update</argument>
<argument>--init</argument>
<argument>test-harness</argument>
</arguments>
</configuration>
</execution>
<execution>
<id>copy-gherkin-tests</id>
<phase>validate</phase>
<goals>
<goal>exec</goal>
</goals>
<configuration>
<!-- copy the feature spec we want to test into resources so them can be easily loaded -->
<!-- run: cp test-harness/features/evaluation.feature src/test/resources/features/ -->
<executable>cp</executable>
<arguments>
<argument>test-harness/features/evaluation.feature</argument>
<argument>src/test/resources/features/</argument>
</arguments>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</profile>
</profiles>

Expand Down
2 changes: 1 addition & 1 deletion providers/flagd/schemas
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
* Helper class to hold configuration default values.
*/
public final class Config {
static final ResolverType DEFAULT_RESOLVER_TYPE = ResolverType.FLAGD;
static final String DEFAULT_PORT = "8013";
static final String DEFAULT_TLS = "false";
static final String DEFAULT_HOST = "localhost";
Expand Down Expand Up @@ -49,4 +50,19 @@ static int fallBackToEnvOrDefault(String key, int defaultValue) {
return defaultValue;
}
}

/**
* flagd resolving type.
*/
public enum ResolverType {
/**
* This is the default resolver type, which connects to flagd instance with flag evaluation gRPC contract.
*/
FLAGD,
/**
* This is the in-process resolving type, where flags are fetched with flag sync gRPC contract and stored
* locally for in-process evaluation.
*/
IN_PROCESS
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
import static dev.openfeature.contrib.providers.flagd.Config.DEFAULT_MAX_CACHE_SIZE;
import static dev.openfeature.contrib.providers.flagd.Config.DEFAULT_MAX_EVENT_STREAM_RETRIES;
import static dev.openfeature.contrib.providers.flagd.Config.DEFAULT_PORT;
import static dev.openfeature.contrib.providers.flagd.Config.DEFAULT_RESOLVER_TYPE;
import static dev.openfeature.contrib.providers.flagd.Config.DEFAULT_TLS;
import static dev.openfeature.contrib.providers.flagd.Config.HOST_ENV_VAR_NAME;
import static dev.openfeature.contrib.providers.flagd.Config.MAX_CACHE_SIZE_ENV_VAR_NAME;
Expand All @@ -33,6 +34,12 @@
@SuppressWarnings("PMD.TooManyStaticImports")
public class FlagdOptions {

/**
* flagd resolving type.
* */
@Builder.Default
private Config.ResolverType resolverType = DEFAULT_RESOLVER_TYPE;

/**
* flagd connection host.
* */
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
package dev.openfeature.contrib.providers.flagd;

import dev.openfeature.contrib.providers.flagd.cache.CacheFactory;
import dev.openfeature.contrib.providers.flagd.grpc.GrpcResolution;
import dev.openfeature.contrib.providers.flagd.resolver.Resolver;
import dev.openfeature.contrib.providers.flagd.resolver.grpc.GrpcResolver;
import dev.openfeature.contrib.providers.flagd.resolver.grpc.cache.CacheFactory;
import dev.openfeature.contrib.providers.flagd.resolver.process.InProcessResolver;
import dev.openfeature.sdk.EvaluationContext;
import dev.openfeature.sdk.EventProvider;
import dev.openfeature.sdk.FeatureProvider;
Expand Down Expand Up @@ -42,7 +44,18 @@ public FlagdProvider() {
* @param options {@link FlagdOptions} with
*/
public FlagdProvider(final FlagdOptions options) {
this.flagResolver = new GrpcResolution(options, CacheFactory.getCache(options), this::getState, this::setState);
switch (options.getResolverType()) {
case IN_PROCESS:
this.flagResolver = new InProcessResolver(options, this::setState);
break;
case FLAGD:
this.flagResolver =
new GrpcResolver(options, CacheFactory.getCache(options), this::getState, this::setState);
break;
default:
throw new IllegalStateException(
String.format("Requested unsupported resolver type of %s", options.getResolverType()));
}
}

@Override
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
package dev.openfeature.contrib.providers.flagd;
package dev.openfeature.contrib.providers.flagd.resolver;

import dev.openfeature.sdk.EvaluationContext;
import dev.openfeature.sdk.ProviderEvaluation;
import dev.openfeature.sdk.Value;

/**
* A generic contract flag resolving contract for flagd.
* A generic flag resolving contract for flagd.
*/
public interface Resolver {
void init() throws Exception;
Expand Down
Loading