Skip to content

Commit

Permalink
chore: add feature-flagging and references (#936)
Browse files Browse the repository at this point in the history
* adds "feature-flagging" section
* rolls "OpenFeature Compliance" into this section... I think it makes
sense here rather than on its own... I have another PR coming soon that
will remove that page and do some related minor restructuring
* makes distinction between custom operation configuration, and
implementation

---------

Signed-off-by: Todd Baert <[email protected]>
  • Loading branch information
toddbaert authored Sep 25, 2023
1 parent 0e584a2 commit c3056b7
Show file tree
Hide file tree
Showing 16 changed files with 709 additions and 52 deletions.
23 changes: 15 additions & 8 deletions mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,6 @@ nav:
- 'Getting started': 'index.md'
- 'Concepts':
- 'Feature Flagging': 'concepts/feature-flagging.md'
- 'OpenFeature Compliance': 'concepts/openfeature-compliance.md'
- 'Syncs': 'concepts/syncs.md'
- 'Architecture': 'architecture.md'
- 'Deployment': 'deployment.md'
Expand All @@ -60,16 +59,24 @@ nav:
- '"start" Command': 'reference/flagd-cli/flagd_start.md'
- '"version" Command': 'reference/flagd-cli/flagd/flagd_version.md'
- 'Sync Configuration': 'reference/sync-configuration.md'
- 'Flag Configuration':
- 'Configuration Overview': 'reference/flag-configuration.md'
- 'Flag Definition':
- 'Definition Overview': 'reference/flag-definition.md'
- 'Custom Operations':
- 'Fractional': 'reference/custom-operations/fractional.md'
- 'String Comparison': 'reference/custom-operations/string-comparision.md'
- 'Semantic Version Comparison': 'reference/custom-operations/semver-comparison.md'
- 'Flag Evaluation Protocol': 'reference/flag-evaluation-protocol.md'
- 'Flag Sync Protocol': 'reference/flag-sync-protocol.md'
- 'Fractional': 'reference/custom-operations/fractional-operation.md'
- 'Semantic Version': 'reference/custom-operations/semver-operation.md'
- 'String Comparison': 'reference/custom-operations/string-comparison-operation.md'
- 'Providers': 'reference/providers.md'
- 'Monitoring': 'reference/monitoring.md'
- 'Specifications':
- 'Flag Evaluation Protocol': 'reference/specifications/flag-evaluation-protocol.md'
- 'Flag Sync Protocol': 'reference/specifications/flag-sync-protocol.md'
- 'RPC Providers': 'reference/specifications/rpc-providers.md'
- 'In-Process Providers': 'reference/specifications/in-process-providers.md'
- 'Custom Operations':
- 'Fractional Specification': 'reference/specifications/custom-operations/fractional-operation-spec.md'
- 'Semantic Version Specification': 'reference/specifications/custom-operations/semver-operation-spec.md'
- 'String Comparison Specification': 'reference/specifications/custom-operations/string-comparison-operation-spec.md'
- 'Naming': 'reference/naming.md'
- 'FAQ': 'faq.md'
- 'Troubleshooting': 'troubleshooting.md'
plugins:
Expand Down
24 changes: 24 additions & 0 deletions web-docs/concepts/feature-flagging.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# Feature Flagging

Feature flags are a software development technique that allows teams to enable, disable or change the behavior of certain features or code paths in a product or service, without modifying the source code.

## OpenFeature Compliance

[OpenFeature](https://openfeature.dev/) is an open standard that provides a vendor-agnostic, community-driven API for feature flagging.
The flagd project is fully OpenFeature-compliant.
In fact, flagd was initially conceived as a reference implementation for an OpenFeature backend, but has become a powerful tool in its own right.
For this reason, you'll find flagd's concepts and terminology align with that of the OpenFeature project.
Within the context of an OpenFeature-compliant feature flag solution, flagd artifacts and libraries comprise the [flag management system](https://openfeature.dev/specification/glossary#flag-management-system) and [providers](https://openfeature.dev/specification/glossary#provider).
These artifacts and libraries alone won't allow you to evaluate flags in your application - you'll also need the [OpenFeature SDK](https://openfeature.dev/specification/glossary#feature-flag-sdk) for your language as well, which provides the evaluation API for application developers to use.

## Supported Feature Flagging Use-Cases

Below is a non-exhaustive table of common feature flag use-cases, and how flagd supports them:

| Use case | flagd Feature |
| ----------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| flag evaluation | Returns the value of a particular feature flag, if the flag is enabled. Supports flags of various types including boolean, numeric, string, and JSON. |
| dynamic configuration | Flag definitions from any sync source are monitored for changes, with some syncs supporting near real time updates. |
| dynamic (context-sensitive) evaluation | flagd evaluations are context sensitive. Rules can use arbitrary context attributes as inputs for flag evaluation logic. |
| fractional evaluation / random assignment | flagd's [fractional](../reference/custom-operations/fractional-operation.md) custom operation supports pseudorandom assignment of flag values. |
| progressive roll-outs | Progressive roll-outs of new features can be accomplished by leveraging the [fractional](../reference/custom-operations/fractional-operation.md) custom operation as well as automation in your build pipeline, SCM, or infrastructure which updates the distribution over time. |
26 changes: 13 additions & 13 deletions web-docs/concepts/syncs.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,20 +13,20 @@ The file path sync provider reads and watch the source file for updates(ex: chan
flagd start --uri file:etc/featureflags.json
```

In this example, `etc/featureflags.json` is a valid feature flag configuration file accessible by the flagd process.
In this example, `etc/featureflags.json` is a valid feature flag definition file accessible by the flagd process.
See [sync source](../reference/sync-configuration.md#source-configuration) configuration for details.

---

### HTTP sync

The HTTP sync provider fetch flags from a remote source and periodically poll the source for flag configuration updates.
The HTTP sync provider fetch flags from a remote source and periodically poll the source for flag definition updates.

```shell
flagd start --uri https://my-flag-source.json
```

In this example, `https://my-flag-source.json` is a remote endpoint responding valid feature flag configurations when
In this example, `https://my-flag-source.json` is a remote endpoint responding valid feature flag definition when
invoked with **HTTP GET** request.
The polling interval, port, TLS settings, and authentication information can be configured.
See [sync source](../reference/sync-configuration.md#source-configuration) configuration for details.
Expand All @@ -35,7 +35,7 @@ See [sync source](../reference/sync-configuration.md#source-configuration) confi

### gRPC sync

The gRPC sync provider streams flag configurations from a gRPC sync provider implementation. This stream connection is ruled
The gRPC sync provider streams flag definition from a gRPC sync provider implementation. This stream connection is ruled
by
the [sync service protobuf definition](https://github.com/open-feature/schemas/blob/main/protobuf/sync/v1/sync_service.proto).

Expand Down Expand Up @@ -68,17 +68,17 @@ See [sync source](../reference/sync-configuration.md#source-configuration) confi

## Merging

Flagd can be configured to read from multiple sources at once, when this is the case flagd will merge all flag configurations into a single
Flagd can be configured to read from multiple sources at once, when this is the case flagd will merge all flag definition into a single
merged state.

For example:

![flag merge 1](../images/flag-merge-1.svg)

In this example, `source-A` and `source-B` provide a single flag configuration, the `foo` flag and the `bar` flag respectively.
The merge logic for this configuration is simple, both flag configurations are added to the `store`.
In this example, `source-A` and `source-B` provide a single flag definition, the `foo` flag and the `bar` flag respectively.
The merge logic for this definition is simple, both flag definition are added to the `store`.

In most scenarios, these flag sources will be supplying `n` number of configurations, using a unique flag key for each configuration.
In most scenarios, these flag sources will be supplying `n` number of definition, using a unique flag key for each definition.

However, as multiple sources are being used, there is the opportunity for keys to be duplicated, intentionally or not, between flag sources.
In these situations `flagd` uses a merge priority order to ensure that its behavior is consistent.
Expand All @@ -91,24 +91,24 @@ Merge order is dictated by the order that `sync-providers` and `uris` are define

When `flagd` is started with the command defined above, `source-B` takes priority over `source-A`, whilst `source-C` takes priority over both `source-B` and `source-A`.

Using the above example, if a flag key is duplicated across all 3 sources, then the configuration from `source-C` would be the only one stored in the merged state.
Using the above example, if a flag key is duplicated across all 3 sources, then the definition from `source-C` would be the only one stored in the merged state.

![flag merge 2](../images/flag-merge-2.svg)

### State Resync Events

Given the above example, the `source-A` and `source-B` 'versions' of flag configuration the `foo` have been discarded, so if a delete event in `source-C` results in the removal of the `foo`flag, there will no longer be any reference of `foo` in flagd's store.
Given the above example, the `source-A` and `source-B` 'versions' of flag definition the `foo` have been discarded, so if a delete event in `source-C` results in the removal of the `foo`flag, there will no longer be any reference of `foo` in flagd's store.

As a result of this flagd will return `FLAG_NOT_FOUND` errors, and the OpenFeature SDK will always return the default value.

To prevent flagd falling out of sync with its flag sources during delete events, resync events are used.
When a delete event results in a flag configuration being removed from the merged state, the full set of configurations is requested from all flag sources, and the merged state is rebuilt.
When a delete event results in a flag definition being removed from the merged state, the full set of definition is requested from all flag sources, and the merged state is rebuilt.
As a result, the value of the `foo` flag from `source-B` will be stored in the merged state, preventing flagd from returning `FLAG_NOT_FOUND` errors.

![flag merge 3](../images/flag-merge-3.svg)

In the example above, a delete event results in a resync event being fired, as `source-C` has deleted its 'version' of the `foo`, this results in a new merge state being formed from the remaining configurations.
In the example above, a delete event results in a resync event being fired, as `source-C` has deleted its 'version' of the `foo`, this results in a new merge state being formed from the remaining definition.

![flag merge 4](../images/flag-merge-4.svg)

Resync events may lead to further resync events if the returned flag configurations result in further delete events, however the state will eventually be resolved correctly.
Resync events may lead to further resync events if the returned flag definition result in further delete events, however the state will eventually be resolved correctly.
138 changes: 138 additions & 0 deletions web-docs/reference/custom-operations/fractional-operation.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
# Fractional Operation

OpenFeature allows clients to pass contextual information which can then be used during a flag evaluation. For example, a client could pass the email address of the user.

```js
// Factional evaluation property name used in a targeting rule
"fractional": [
// Evaluation context property used to determine the split
{ "var": "email" },
// Split definitions contain an array with a variant and percentage
// Percentages must add up to 100
[
// Must match a variant defined in the flag definition
"red",
// The probability this variant is selected
50
],
[
// Must match a variant defined in the flag definition
"green",
// The probability this variant is selected
50
]
]
```

See the [headerColor](https://github.com/open-feature/flagd/blob/main/samples/example_flags.flagd.json#L88-#L133) flag.
The `defaultVariant` is `red`, but it contains a [targeting rule](reusable_targeting_rules.md), meaning a fractional evaluation occurs for flag evaluation with a `context` object containing `email` and where that `email` value contains `@faas.com`.

In this case, `25%` of the evaluations will receive `red`, `25%` will receive `blue`, and so on.

Assignment is deterministic (sticky) based on the expression supplied as the first parameter (`{ "var": "email" }`, in this case).
The value retrieved by this expression is referred to as the "bucketing value".
The bucketing value expression can be omitted, in which case a concatenation of the `targetingKey` and the `flagKey` will be used.

The `fractional` operation is a custom JsonLogic operation which deterministically selects a variant based on
the defined distribution of each variant (as a percentage).
This works by hashing ([murmur3](https://github.com/aappleby/smhasher/blob/master/src/MurmurHash3.cpp))
the given data point, converting it into an int in the range [0, 99].
Whichever range this int falls in decides which variant
is selected.
As hashing is deterministic we can be sure to get the same result every time for the same data point.

The `fractional` operation can be added as part of a targeting definition.
The value is an array and the first element is the name of the property to use from the evaluation context.
This value should typically be something that remains consistent for the duration of a users session (e.g. email or session ID).
The other elements in the array are nested arrays with the first element representing a variant and the second being the percentage that this option is selected.
There is no limit to the number of elements but the configured percentages must add up to 100.

## Example

Flags defined as such:

```json
{
"flags": {
"headerColor": {
"variants": {
"red": "#FF0000",
"blue": "#0000FF",
"green": "#00FF00"
},
"defaultVariant": "red",
"state": "ENABLED",
"targeting": {
"fractional": [
{ "var": "email" },
[
"red",
50
],
[
"blue",
20
],
[
"green",
30
]
]
}
}
}
}
```

will return variant `red` 50% of the time, `blue` 20% of the time & `green` 30% of the time.

Command:

```shell
curl -X POST "localhost:8013/schema.v1.Service/ResolveString" -d '{"flagKey":"headerColor","context":{"email": "[email protected]"}}' -H "Content-Type: application/json"
```

Result:

```shell
{"value":"#0000FF","reason":"TARGETING_MATCH","variant":"blue"}
```

Command:

```shell
curl -X POST "localhost:8013/schema.v1.Service/ResolveString" -d '{"flagKey":"headerColor","context":{"email": "[email protected]"}}' -H "Content-Type: application/json"
```

Result:

```json
{"value":"#00FF00","reason":"TARGETING_MATCH","variant":"green"}
```

Notice that rerunning either curl command will always return the same variant and value.
The only way to get a different value is to change the email or update the `fractional` configuration.

### Migrating from legacy "fractionalEvaluation"

If you are using a legacy fractional evaluation (`fractionalEvaluation`), it's recommended you migrate to `fractional`.
The new `fractional` evaluator supports nested properties and JsonLogic expressions.
To migrate, simply use a JsonLogic variable declaration for the bucketing property, instead of a string:

old:

```json
"fractionalEvaluation": [
"email",
[ "red", 25 ], [ "blue", 25 ], [ "green", 25 ], [ "yellow", 25 ]
]
```

new:

```json
"fractional": [
{ "var": "email" },
[ "red", 25 ], [ "blue", 25 ], [ "green", 25 ], [ "yellow", 25 ]
]
```
80 changes: 80 additions & 0 deletions web-docs/reference/custom-operations/semver-operation.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
# Semantic Version Operation

OpenFeature allows clients to pass contextual information which can then be used during a flag evaluation. For example, a client could pass the email address of the user.

In some scenarios, it is desirable to use that contextual information to segment the user population further and thus return dynamic values.

The `sem_ver` evaluation checks if the given property matches a semantic versioning condition.
It returns 'true', if the value of the given property meets the condition, 'false' if not.
Note that the 'sem_ver' evaluation rule must contain exactly three items:

1. Target property: this needs which both resolve to a semantic versioning string
2. Operator: One of the following: `=`, `!=`, `>`, `<`, `>=`, `<=`, `~` (match minor version), `^` (match major version)
3. Target value: this needs which both resolve to a semantic versioning string

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
]
}
```

## Example for 'sem_ver' Evaluation

Flags defined as such:

```json
{
"flags": {
"headerColor": {
"variants": {
"red": "#FF0000",
"blue": "#0000FF",
"green": "#00FF00"
},
"defaultVariant": "blue",
"state": "ENABLED",
"targeting": {
"if": [
{
"sem_ver": [{"var": "version"}, ">=", "1.0.0"]
},
"red", "green"
]
}
}
}
}
```

will return variant `red`, if the value of the `version` is a semantic version that is greater than or equal to `1.0.0`.

Command:

```shell
curl -X POST "localhost:8013/schema.v1.Service/ResolveString" -d '{"flagKey":"headerColor","context":{"version": "1.0.1"}}' -H "Content-Type: application/json"
```

Result:

```json
{"value":"#00FF00","reason":"TARGETING_MATCH","variant":"red"}
```

Command:

```shell
curl -X POST "localhost:8013/schema.v1.Service/ResolveString" -d '{"flagKey":"headerColor","context":{"version": "0.1.0"}}' -H "Content-Type: application/json"
```

Result:

```shell
{"value":"#0000FF","reason":"TARGETING_MATCH","variant":"green"}
```
Loading

0 comments on commit c3056b7

Please sign in to comment.