-
Notifications
You must be signed in to change notification settings - Fork 0
feat: Add OpenFeature provider #111
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
Changes from all commits
8ec0bfe
4ba1e0b
d751c74
5cbe419
c571f96
d1eaae9
0fe9a8d
5d2e491
4797103
caca0ca
bba7d2b
40fdc2f
126a8d8
c276a6f
2277a4a
e9bdc10
3146825
7c08414
ef5d64a
1c9802a
5a3b46d
7bbceb4
0e6dccd
a7d69c2
4848f52
ea03e6b
5424e87
a1aaa0c
4e21dc9
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,88 @@ | ||
| # DevCycle Java SDK OpenFeature Provider | ||
|
|
||
| This SDK provides a Java implementation of the [OpenFeature](https://openfeature.dev/) Provider interface. | ||
|
|
||
| ## Example App | ||
|
|
||
| See the [example app](src/examples/java/com/devcycle/examples/OpenFeatureExample.java) for a working example of the DevCycle Java SDK OpenFeature Provider. | ||
|
|
||
| ## Usage | ||
|
|
||
| Start by creating the appropriate DevCycle SDK client (`DevCycleLocalClient` or `DevCycleCloudClient`). | ||
|
|
||
| See our [Java Cloud Bucketing SDK](https://docs.devcycle.com/sdk/server-side-sdks/java-cloud) and [Java Local Bucketing SDK](https://docs.devcycle.com/sdk/server-side-sdks/java-local) documentation for more information on how to configure the SDK. | ||
|
|
||
| ```java | ||
| // Initialize DevCycle Client | ||
| DevCycleLocalOptions options = DevCycleLocalOptions.builder().build(); | ||
| DevCycleLocalClient devCycleClient = new DevCycleLocalClient("DEVCYCLE_SERVER_SDK_KEY", options); | ||
|
|
||
| // Set the initialzed DevCycle client as the provider for OpenFeature | ||
| OpenFeatureAPI api = OpenFeatureAPI.getInstance(); | ||
| api.setProvider(devCycleClient.getOpenFeatureProvider()); | ||
|
|
||
| // Get the OpenFeature client | ||
| Client openFeatureClient = api.getClient(); | ||
|
|
||
| // Create the evaluation context to use for fetching variable values | ||
| EvaluationContext context = new MutableContext("user-1234"); | ||
|
|
||
| // Retrieve a boolean flag from the OpenFeature client | ||
| Boolean variableValue = openFeatureClient.getBooleanValue(VARIABLE_KEY, false, context); | ||
| ``` | ||
|
|
||
| ### Required Targeting Key | ||
|
|
||
| For DevCycle SDK to work we require either a `targeting key` or `user_id` attribute to be set on the OpenFeature context. | ||
| This value is used to identify the user as the `user_id` property for a `DevCycleUser` in DevCycle. | ||
|
|
||
| ### Mapping Context Properties to DevCycleUser | ||
|
|
||
| The provider will automatically translate known `DevCycleUser` properties from the OpenFeature context to the `DevCycleUser` object. | ||
| [DevCycleUser Java Interface](https://github.com/DevCycleHQ/java-server-sdk/blob/main/src/main/java/com/devcycle/sdk/server/common/model/DevCycleUser.java) | ||
|
|
||
| For example all these properties will be set on the `DevCycleUser`: | ||
| ```java | ||
| MutableContext context = new MutableContext("test-1234"); | ||
| context.add("email", "[email protected]"); | ||
| context.add("name", "name"); | ||
| context.add("country", "CA"); | ||
| context.add("language", "en"); | ||
| context.add("appVersion", "1.0.11"); | ||
| context.add("appBuild", 1000); | ||
|
|
||
| Map<String,Object> customData = new LinkedHashMap<>(); | ||
| customData.put("custom", "value"); | ||
| context.add("customData", Structure.mapToStructure(customData)); | ||
|
|
||
| Map<String,Object> privateCustomData = new LinkedHashMap<>(); | ||
| privateCustomData.put("private", "data"); | ||
| context.add("privateCustomData", Structure.mapToStructure(privateCustomData)); | ||
| ``` | ||
|
|
||
| Context properties that are not known `DevCycleUser` properties will be automatically | ||
| added to the `customData` property of the `DevCycleUser`. | ||
|
|
||
| DevCycle allows the following data types for custom data values: **boolean**, **integer**, **double**, **float**, and **String**. Other data types will be ignored | ||
|
|
||
| ### JSON Flag Limitations | ||
|
|
||
| The OpenFeature spec for JSON flags allows for any type of valid JSON value to be set as the flag value. | ||
|
|
||
| For example the following are all valid default value types to use with OpenFeature: | ||
| ```java | ||
| // Invalid JSON values for the DevCycle SDK, will return defaults | ||
| openFeatureClient.getObjectValue("json-flag", new Value(new ArrayList<String>(Arrays.asList("value1", "value2")))); | ||
| openFeatureClient.getObjectValue("json-flag", new Value(610)); | ||
| openFeatureClient.getObjectValue("json-flag", new Value(false)); | ||
| openFeatureClient.getObjectValue("json-flag", new Value("string")); | ||
| openFeatureClient.getObjectValue("json-flag", new Value()); | ||
| ``` | ||
|
|
||
| However, these are not valid types for the DevCycle SDK, the DevCycle SDK only supports JSON Objects: | ||
| ```java | ||
|
|
||
| Map<String,Object> defaultJsonData = new LinkedHashMap<>(); | ||
| defaultJsonData.put("default", "value"); | ||
| openFeatureClient.getObjectValue("json-flag", new Value(Structure.mapToStructure(defaultJsonData))); | ||
| ``` | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,85 @@ | ||
| package com.devcycle.examples; | ||
|
|
||
| import com.devcycle.sdk.server.local.api.DevCycleLocalClient; | ||
| import com.devcycle.sdk.server.local.model.DevCycleLocalOptions; | ||
| import dev.openfeature.sdk.*; | ||
|
|
||
| import java.util.LinkedHashMap; | ||
| import java.util.Map; | ||
|
|
||
| public class OpenFeatureExample { | ||
| public static void main(String[] args) throws InterruptedException { | ||
| String server_sdk_key = System.getenv("DEVCYCLE_SERVER_SDK_KEY"); | ||
| if (server_sdk_key == null) { | ||
| System.err.println("Please set the DEVCYCLE_SERVER_SDK_KEY environment variable"); | ||
| System.exit(1); | ||
| } | ||
|
|
||
| DevCycleLocalOptions options = DevCycleLocalOptions.builder().configPollingIntervalMS(60000) | ||
| .disableAutomaticEventLogging(false).disableCustomEventLogging(false).build(); | ||
|
|
||
| // Initialize DevCycle Client | ||
| DevCycleLocalClient devCycleClient = new DevCycleLocalClient(server_sdk_key, options); | ||
|
|
||
| for (int i = 0; i < 10; i++) { | ||
| if (devCycleClient.isInitialized()) { | ||
| break; | ||
| } | ||
| Thread.sleep(500); | ||
| } | ||
|
Comment on lines
+22
to
+29
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It depends on how DevCycle users already use the product, but if you want to avoid something like this in OpenFeature use-cases, you could implement the getState method, and return |
||
|
|
||
| // Setup OpenFeature with the DevCycle Provider | ||
| OpenFeatureAPI api = OpenFeatureAPI.getInstance(); | ||
| api.setProvider(devCycleClient.getOpenFeatureProvider()); | ||
|
|
||
| Client openFeatureClient = api.getClient(); | ||
|
|
||
| // Create the evaluation context to use for fetching variable values | ||
| MutableContext context = new MutableContext("test-1234"); | ||
| context.add("email", "[email protected]"); | ||
| context.add("name", "Test User"); | ||
| context.add("language", "en"); | ||
| context.add("country", "CA"); | ||
| context.add("appVersion", "1.0.0"); | ||
| context.add("appBuild", "1"); | ||
| context.add("deviceModel", "Macbook"); | ||
|
|
||
| // Add Devcycle Custom Data values | ||
| Map<String,Object> customData = new LinkedHashMap<>(); | ||
| customData.put("custom", "value"); | ||
| context.add("customData", Structure.mapToStructure(customData)); | ||
|
|
||
| // Add Devcycle Private Custom Data values | ||
| Map<String,Object> privateCustomData = new LinkedHashMap<>(); | ||
| privateCustomData.put("private", "data"); | ||
| context.add("privateCustomData", Structure.mapToStructure(privateCustomData)); | ||
|
|
||
| // The default value can be of type string, boolean, number, or JSON | ||
| Boolean defaultValue = false; | ||
|
|
||
| // Fetch variable values using the identifier key, with a default value and user | ||
| // object. The default value can be of type string, boolean, number, or JSON | ||
| Boolean variableValue = openFeatureClient.getBooleanValue("test-boolean-variable", defaultValue, context); | ||
|
|
||
| // Use variable value | ||
| if (variableValue) { | ||
| System.out.println("feature is enabled"); | ||
| } else { | ||
| System.out.println("feature is NOT enabled"); | ||
| } | ||
|
|
||
| // Default JSON objects must be a map of string to primitive values | ||
| Map<String, Object> defaultJsonData = new LinkedHashMap<>(); | ||
| defaultJsonData.put("default", "value"); | ||
|
|
||
| // Fetch a JSON object variable | ||
| Value jsonObject = openFeatureClient.getObjectValue("test-json-variable", new Value(Structure.mapToStructure(defaultJsonData)), context); | ||
| System.out.println(jsonObject.toString()); | ||
|
|
||
| // Retrieving a string variable along with the resolution details | ||
| FlagEvaluationDetails<String> details = openFeatureClient.getStringDetails("doesnt-exist", "default", context); | ||
| System.out.println("Value: " + details.getValue()); | ||
| System.out.println("Reason: " + details.getReason()); | ||
|
|
||
| } | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.