-
Notifications
You must be signed in to change notification settings - Fork 113
docs: add specification for in-process flagd provider implementations #848
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
toddbaert
merged 20 commits into
open-feature:main
from
bacherfl:doc/in-process-provider-spec
Aug 24, 2023
Merged
Changes from all commits
Commits
Show all changes
20 commits
Select commit
Hold shift + click to select a range
e7d07d3
docs: add specification for in-process flagd provider implementations
bacherfl 47cdf1b
fix linting errors
bacherfl de147ca
Update docs/other_resources/in-process-providers/specification.md
bacherfl e29df8a
Update docs/other_resources/in-process-providers/specification.md
bacherfl 2e53626
Update docs/other_resources/in-process-providers/specification.md
bacherfl 96d541c
Update docs/other_resources/in-process-providers/specification.md
bacherfl 584fab5
Update docs/other_resources/in-process-providers/evaluators/fractiona…
bacherfl 2dae7cb
Update docs/other_resources/in-process-providers/specification.md
bacherfl 6520137
Update docs/other_resources/in-process-providers/specification.md
bacherfl e7c32cb
switch to relative links
bacherfl dadb25d
added paragraph explaining the difference between in process provider…
bacherfl 1409f9f
fix markdown linting error
bacherfl e073ad7
Update docs/other_resources/in-process-providers/evaluators/semver_ev…
bacherfl ee2e37e
Update docs/other_resources/in-process-providers/evaluators/string_co…
bacherfl 0d08652
adapted the fractional evaluation spec
bacherfl 60aa1b6
Merge branch 'main' into doc/in-process-provider-spec
toddbaert 8fd72cc
Update docs/other_resources/in-process-providers/evaluators/fractiona…
bacherfl e317cda
Update docs/other_resources/in-process-providers/specification.md
bacherfl 2eeb575
adapted to pr review comments
bacherfl 38dd3b2
Merge branch 'main' into doc/in-process-provider-spec
toddbaert File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
192 changes: 192 additions & 0 deletions
192
docs/other_resources/in-process-providers/evaluators/fractional_evaluation.md
This file contains hidden or 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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,192 @@ | ||
| # Fractional Evaluation | ||
|
|
||
| This evaluator allows to split the returned variants of a feature flag into different buckets, | ||
| where each bucket can be assigned a percentage, representing how many requests will resolve to the corresponding | ||
| variant. The sum of all weights must be 100, and the distribution must be performed by using the value of a referenced | ||
| from the evaluation context to hash that value and map it to a value between [0, 100]. It is important to note | ||
| that evaluations MUST be sticky, meaning that flag resolution requests containing the same value for the | ||
| referenced property in their context MUST always resolve to the same variant. For calculating the hash value of the | ||
| referenced evaluation context property, the [MurmurHash3](https://github.com/aappleby/smhasher/blob/master/src/MurmurHash3.cpp) | ||
| hash function should be used. This is to ensure that flag resolution requests yield the same result, | ||
| regardless of which implementation of the in-process flagd provider is being used. | ||
|
|
||
| array containing at least two items, with the first item being an optional [json logic variable declaration](https://jsonlogic.com/operations.html#var) | ||
| specifying the target property to base the distribution of values on. If not supplied, a concatination of the | ||
| `flagKey` and `targetingKey` are used: `{"cat": [{"var":"$flagd.flag_key"}, {"var":"user.email"}]}`. | ||
| The remaining items are `arrays`, each with two values, with the first being `string` item representing the name of the variant, and the | ||
| second being a `float` item representing the percentage for that variant. The percentages of all items must add up to | ||
| 100.0, otherwise unexpected behavior can occur during the evaluation. The `data` object can be an arbitrary | ||
| JSON object. Below is an example for a targetingRule containing a `fractionalEvaluation`: | ||
|
|
||
| ```json | ||
| { | ||
| "flags": { | ||
| "headerColor": { | ||
| "variants": { | ||
| "red": "#FF0000", | ||
| "blue": "#0000FF", | ||
| "green": "#00FF00" | ||
| }, | ||
| "defaultVariant": "red", | ||
| "state": "ENABLED", | ||
| "targeting": { | ||
| "fractionalEvaluation": [ | ||
| {"var":"email"}, | ||
| [ | ||
| "red", | ||
| 50 | ||
| ], | ||
| [ | ||
| "blue", | ||
| 20 | ||
| ], | ||
| [ | ||
| "green", | ||
| 30 | ||
| ] | ||
| ] | ||
| } | ||
| } | ||
| } | ||
| } | ||
| ``` | ||
|
|
||
| Please note that the implementation of this evaluator can assume that instead of `{"var": "email"}`, it will receive | ||
| the resolved value of that referenced property, as resolving the value will be taken care of by JsonLogic before | ||
| applying the evaluator. | ||
|
|
||
| The following flow chart depicts the logic of this evaluator: | ||
|
|
||
| ```mermaid | ||
| flowchart TD | ||
| A[Parse targetingRule] --> B{Is an array containing at least two items?}; | ||
| B -- Yes --> C{Is targetingRule at index 0 a string?}; | ||
| B -- No --> D[Return nil]; | ||
| C -- Yes --> E[targetPropertyValue := targetingRule at index 0]; | ||
| C -- No --> D; | ||
| E -- Yes --> F[Iterate through the remaining elements of the targetingRule array and parse the variants and their percentages]; | ||
| F --> G{Parsing successful?}; | ||
| G -- No --> D; | ||
| G -- Yes --> H{Does percentage of variants add up to 100?}; | ||
| H -- No --> D; | ||
| H -- Yes --> I[hash := murmur3Hash of targetPropertyValue divided by Int64.MaxValue] | ||
| I --> L[Iterate through the variant and increment the threshold by the percentage of each variant. Return the first variant where the bucket is smaller than the threshold.] | ||
| ``` | ||
|
|
||
| As a reference, below is a simplified version of the actual implementation of this evaluator in Go. | ||
|
|
||
| ```go | ||
|
|
||
| type fractionalEvaluationDistribution struct { | ||
| variant string | ||
| percentage int | ||
| } | ||
|
|
||
| /* | ||
| values: contains the targeting rule object; e.g.: | ||
| [ | ||
| {"var":"email"}, | ||
| [ | ||
| "red", | ||
| 50 | ||
| ], | ||
| [ | ||
| "blue", | ||
| 20 | ||
| ], | ||
| [ | ||
| "green", | ||
| 30 | ||
| ] | ||
| ] | ||
|
|
||
| data: contains the evaluation context; e.g.: | ||
| { | ||
| "email": "test@faas.com" | ||
| } | ||
| */ | ||
| func FractionalEvaluation(values, data interface{}) interface{} { | ||
| // 1. Check if the values object contains at least two elements: | ||
| valuesArray, ok := values.([]interface{}) | ||
| if !ok { | ||
| log.Error("fractional evaluation data is not an array") | ||
| return nil | ||
| } | ||
| if len(valuesArray) < 2 { | ||
| log.Error("fractional evaluation data has length under 2") | ||
| return nil | ||
| } | ||
|
|
||
| // 2. Get the target property value used for bucketing the values | ||
| valueToDistribute, ok := valuesArray[0].(string) | ||
| if !ok { | ||
| log.Error("first element of fractional evaluation data isn't of type string") | ||
| return nil | ||
| } | ||
|
|
||
| // 3. Parse the fractionalEvaluation values distribution | ||
| sumOfPercentages := 0 | ||
| var feDistributions []fractionalEvaluationDistribution | ||
|
|
||
| // start at index 1, as the first item of the values array is the target property | ||
| for i := 1; i < len(valuesArray); i++ { | ||
| distributionArray, ok := values[i].([]interface{}) | ||
| if !ok { | ||
| log.Error("distribution elements aren't of type []interface{}") | ||
| return nil | ||
| } | ||
|
|
||
| if len(distributionArray) != 2 { | ||
| log.Error("distribution element isn't length 2") | ||
| return nil | ||
| } | ||
|
|
||
| variant, ok := distributionArray[0].(string) | ||
| if !ok { | ||
| log.Error("first element of distribution element isn't a string") | ||
| return nil | ||
| } | ||
|
|
||
| percentage, ok := distributionArray[1].(float64) | ||
| if !ok { | ||
| log.Error("second element of distribution element isn't float") | ||
| return nil | ||
| } | ||
|
|
||
| sumOfPercentages += int(percentage) | ||
|
|
||
| feDistributions = append(feDistributions, fractionalEvaluationDistribution{ | ||
| variant: variant, | ||
| percentage: int(percentage), | ||
| }) | ||
| } | ||
|
|
||
| // check if the sum of percentages adds up to 100, otherwise log an error | ||
| if sumOfPercentages != 100 { | ||
| log.Error("percentages must sum to 100, got: %d", sumOfPercentages) | ||
| return nil | ||
| } | ||
|
|
||
| // 4. Calculate the hash of the target property and map it to a number between [0, 99] | ||
| hashValue := murmur3.HashString(value) | ||
|
|
||
| // divide the hash value by the largest possible value, integer 2^64 | ||
| hashRatio := float64(hashValue) / math.Pow(2, 64) | ||
|
|
||
| // integer in range [0, 99] | ||
| bucket := int(hashRatio * 100) | ||
|
|
||
| // 5. Iterate through the variant and increment the threshold by the percentage of each variant. | ||
| // return the first variant where the bucket is smaller than the threshold. | ||
| rangeEnd := 0 | ||
| for _, dist := range feDistribution { | ||
| rangeEnd += dist.percentage | ||
| if bucket < rangeEnd { | ||
| // return the matching variant | ||
| return dist.variant | ||
| } | ||
| } | ||
|
|
||
| return "" | ||
| } | ||
| ``` |
45 changes: 45 additions & 0 deletions
45
docs/other_resources/in-process-providers/evaluators/semver_evaluation.md
This file contains hidden or 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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,45 @@ | ||
| # Semantic Versioning Evaluation | ||
|
|
||
| This evaluator checks if the given property within the evaluation context matches a semantic versioning condition. | ||
| It returns 'true', if the value of the given property meets the condition, 'false' if not. | ||
|
|
||
| The implementation of this evaluator should accept the object containing the `sem_ver` evaluator | ||
| configuration, and a `data` object containing the evaluation context. | ||
| The 'sem_ver' evaluation rule contains exactly three items: | ||
|
|
||
| 1. Target property value: the resolved value of the target property referenced in the targeting rule | ||
| 2. Operator: One of the following: `=`, `!=`, `>`, `<`, `>=`, `<=`, `~` (match minor version), `^` (match major version) | ||
| 3. Target value: this needs to resolve to a semantic versioning string. If this condition is not met, the evaluator should | ||
| log an appropriate error message and return `nil` | ||
|
|
||
| The `sem_ver` evaluation returns a boolean, indicating whether the condition has been met. | ||
|
|
||
| ```js | ||
| { | ||
| "if": [ | ||
| { | ||
| "sem_ver": [{"var": "version"}, ">=", "1.0.0"] | ||
| }, | ||
| "red", null | ||
| ] | ||
| } | ||
| ``` | ||
|
|
||
| Please note that the implementation of this evaluator can assume that instead of `{"var": "version"}`, it will receive | ||
| the resolved value of that referenced property, as resolving the value will be taken care of by JsonLogic before | ||
| applying the evaluator. | ||
|
|
||
| The following flow chart depicts the logic of this evaluator: | ||
|
|
||
| ```mermaid | ||
| flowchart TD | ||
| A[Parse targetingRule] --> B{Is an array containing exactly three items?}; | ||
| B -- Yes --> C{Is targetingRule at index 0 a semantic version string?}; | ||
| B -- No --> D[Return nil]; | ||
| C -- Yes --> E{Is targetingRule at index 1 a supported operator?}; | ||
| C -- No --> D; | ||
| E -- Yes --> F{Is targetingRule at index 2 a semantic version string?}; | ||
| E -- No --> D; | ||
| F -- No --> D; | ||
| F --> G[Compare the two versions using the operator and return a boolean value indicating if they match]; | ||
| ``` | ||
40 changes: 40 additions & 0 deletions
40
...other_resources/in-process-providers/evaluators/string_comparison_evaluation.md
This file contains hidden or 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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,40 @@ | ||
| # StartsWith/EndsWith evaluation | ||
|
|
||
| This evaluator selects a variant based on whether the specified property within the evaluation context | ||
| starts/ends with a certain string. | ||
|
|
||
| The implementation of this evaluator should accept the object containing the `starts_with` or `ends_with` evaluator | ||
| configuration, and a `data` object containing the evaluation context. | ||
| The `starts_with`/`ends_with` evaluation rule contains exactly two items: | ||
|
|
||
| 1. The resolved string value from the evaluation context | ||
| 2. The target string value | ||
|
|
||
| The `starts_with`/`ends_with` evaluation returns a boolean, indicating whether the condition has been met. | ||
|
|
||
| ```js | ||
| // starts_with property name used in a targeting rule | ||
| "starts_with": [ | ||
| // Evaluation context property the be evaluated | ||
| {"var": "email"}, | ||
| // prefix that has to be present in the value of the referenced property | ||
| "user@faas" | ||
| ] | ||
| ``` | ||
|
|
||
| Please note that the implementation of this evaluator can assume that instead of `{"var": "email"}`, it will receive | ||
| the resolved value of that referenced property, as resolving the value will be taken care of by JsonLogic before | ||
| applying the evaluator. | ||
|
|
||
| The following flow chart depicts the logic of this evaluator: | ||
|
|
||
| ```mermaid | ||
| flowchart TD | ||
| A[Parse targetingRule] --> B{Is an array containing exactly two items?}; | ||
| B -- Yes --> C{Is targetingRule at index 0 a string?}; | ||
| B -- No --> D[Return nil]; | ||
| C -- Yes --> E{Is targetingRule at index 1 a string?}; | ||
| C -- No --> D; | ||
|
bacherfl marked this conversation as resolved.
|
||
| E -- No --> D; | ||
| E --> F[Return a boolean value indicating if the first string starts/ends with the second string]; | ||
| ``` | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.