diff --git a/docs/reference/specifications/in-process-providers.md b/docs/reference/specifications/in-process-providers.md deleted file mode 100755 index 090f72bbe..000000000 --- a/docs/reference/specifications/in-process-providers.md +++ /dev/null @@ -1,260 +0,0 @@ ---- -description: flagd in-process proivider specification ---- - -# Creating an in-process flagd provider - -An in-process flagd provider is designed to be embedded into the application, and therefore no communication outside the process of the application for feature flag evaluation is needed. -This can be desirable in some cases, particularly if latency is a concern. - -The in-process flagd provider is responsible for creating an abstraction between the [JsonLogic](https://jsonlogic.com) based evaluation of flag configurations following the [flag configuration scheme](https://github.com/open-feature/schemas/blob/main/json/flagd-definitions.json) used by `flagd` and the OpenFeature SDK (for the [chosen technology](https://openfeature.dev/docs/reference/technologies/)). - -Prerequisites: - -- Understanding of [general provider concepts](https://openfeature.dev/docs/reference/concepts/provider/) -- Proficiency in the chosen programming language (check the language isn't already covered by the [existing providers](https://openfeature.dev/ecosystem?instant_search%5BrefinementList%5D%5Bvendor%5D%5B0%5D=flagd)) - -The Flag definition containing the feature flags and JsonLogic based targeting rules shall be retrieved by the -in-process flagd provider via a gRPC client connection to a sync server, such as [flagd-proxy](https://github.com/open-feature/flagd/tree/main/flagd-proxy). - -## Sync source - -An implementation of an in-process flagd-provider must accept the following environment variables which determine the sync source: - -- `FLAGD_SOURCE_URI`: The URI identifying the sync source. Depending on the sync provider type, this can be the URI of a gRPC service providing the `sync` API required by the in-process flagd provider, or the name of a [core.openfeature.dev/v1beta1.FeatureFlag](https://github.com/open-feature/open-feature-operator/blob/main/docs/crds.md#featureflag-1) Custom Resource containing the flag definition. -- `FLAGD_SOURCE_PROVIDER_TYPE`: The type of the provider. E.g. `grpc` or `kubernetes`. -- `FLAGD_SOURCE_SELECTOR`: Optional selector for the feature flag definition of interest. This is used as a `selector` for the flagd-proxie's sync API to identify a flag definition within a collection of feature flag definitions. - -An implementation of an in-process flagd provider should provide a source for retrieving the flag definition, namely a gRPC source. -Other sources may be desired eventually, so separation of concerns should be maintained between the abstractions evaluating flags and those retrieving confirmation. - -## gRPC sources - -gRPC sync sources are identified by the `provider` field set to `grpc`. -When such a sync source is specified, the in-process flagd provider should connect to the gRPC service located at the `uri` of the sync source, and use its [sync API](./protos.md#syncv1sync_serviceproto) to retrieve the feature flag definition. -If the `selector` field of the sync source is set, that selector should be passed through to the `Sync` and `FetchAllFlags` requests sent to the gRPC server. - -### Protobuf - -Protobuf schemas define the contract between a client (flagd or the in-process provider implementation) and server (`flagd-proxy`). -`flagd-proxy`'s schemas are defined [here](https://github.com/open-feature/schemas/tree/main/protobuf/sync/v1). - -#### Code generation for gRPC sync - -Leverage the [buf CLI](https://docs.buf.build/installation) or [protoc](https://grpc.io/docs/protoc-installation/) to generate a `flagd-proxy` client in the chosen technology: - -Add the [open-feature schema repository](https://github.com/open-feature/schemas) as a submodule - -```shell -git submodule add --force https://github.com/open-feature/schemas.git -``` - -Create a `buf.gen.{chosen language}.yaml` for the chosen language in `schemas/protobuf` (if it doesn't already exist) using one of the other files as a template (find a plugin for the chosen language [here](https://buf.build/protocolbuffers/plugins)) and create a pull request with this file. - -Generate the code (this step ought to be automated in the build process for the chosen technology so that the generated code is never committed) - -```shell -cd schemas/protobuf -buf generate --template buf.gen.{chosen language}.yaml -``` - -As an alternative to buf, use the .proto file directly along with whatever protoc-related tools or plugins avaialble for your language. - -Move the generated code (following convention for the chosen language) and add its location to .gitignore - -Note that for the in-process provider only the `sync` package will be relevant, as it does not communicate with `flagd`, but only with compliant gRPC services such as `flagd-proxy`. - -## JsonLogic evaluation - -An in-process flagd provider should provide the feature set offered by [JsonLogic](https://jsonlogic.com) to evaluate flag resolution requests for a given context. -If available, the JsonLogic library for the chosen technology should be used. -Additionally, it should also provide the custom JsonLogic evaluators and `$flagd` properties in the evaluation context described below. - -### Custom JsonLogic evaluators - -In addition to the built-in evaluators provided by JsonLogic, the following custom targeting rules should be implemented by the provider: - -- [Fractional operation](../../reference/custom-operations/fractional-operation.md): -This evaluator allows splitting of 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. For more specific implementation guidelines, please refer to -[this document](./custom-operations/fractional-operation-spec.md). -- [Semantic version evaluation](../../reference/custom-operations/semver-operation.md): -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. -For more specific implementation guidelines, please refer to [this document](../specifications/custom-operations/semver-operation-spec.md). -- [StartsWith/EndsWith evaluation](../../reference/custom-operations/string-comparison-operation.md): -This evaluator selects a variant based on whether the specified property within the evaluation context -starts/ends with a certain string. -For more specific implementation guidelines, please refer to [this document](./custom-operations/string-comparison-operation-spec.md). - -### Targeting key - -An in-process provider should map the [targeting-key](https://openfeature.dev/specification/glossary#targeting-key) into a top level property of the context used in rules, with the key `"targetingKey"`. - -### $flagd properties in the evaluation context - -An in-process flagd provider should also add the following properties to the JsonLogic evaluation context so that users can use them in their targeting rules. -Conflicting properties in the context will be overwritten by the values below. - -| Property | Description | -|----------|-------------| -| `$flagd.flagKey` | the identifier for the flag being evaluated | -| `$flagd.timestamp`| a unix timestamp (in seconds) of the time of evaluation | - -## Provider construction - -(**using Go as an example**) - -Create a provider struct/class/type (whichever is relevant to the chosen language) with an exported (public) constructor allowing configuration (e.g. `flagd` host). -Give the provider an un-exported (private) client field, set this field as the client generated by the previous step. - -Create methods for the provider to satisfy the chosen language SDK's provider interface. -These methods ought to wrap the built client's methods. - -```go -type Provider struct { - evaluator IJsonEvaluator -} - -type ProviderOption func(*Provider) - -func NewProvider(options ...ProviderOption) *Provider { - provider := &Provider{} - for _, opt := range opts { - opt(provider) - } - - // create a store that is responsible for retrieving the flag configurations - // from the sources that are given to the provider via the options - s := store.NewFlags() - s.FlagSources = append(s.FlagSources, os.Getenv("FLAGD_SOURCE_URI")) - s.SourceMetadata[provider.URI] = store.SourceDetails{ - Source: os.Getenv("FLAGD_SOURCE_URI"), - Selector: os.Getenv("FLAGD_SOURCE_SELECTOR")), - } - - // derive evaluator - provider.evaluator := setupJSONEvaluator(logger, s) - - return provider -} - -func WithHost(host string) ProviderOption { - return func(p *Provider) { - p.flagdHost = host - } -} - -func (p *Provider) BooleanEvaluation( - ctx context.Context, flagKey string, defaultValue bool, evalCtx of.FlattenedContext, -) of.BoolResolutionDetail { - - res, err := p.evaluator.ResolveBoolean(ctx, flagKey, context) - - if err != nil { - return of.BoolResolutionDetail{ - Value: defaultValue, - ProviderResolutionDetail: of.ProviderResolutionDetail{ - ResolutionError: of.NewGeneralResolutionError(err.Error()), - Reason: of.Reason(res.Reason), - Variant: res.Variant, - }, - } - } - - return of.BoolResolutionDetail{ - Value: defaultValue, - ProviderResolutionDetail: of.ProviderResolutionDetail{ - Reason: of.Reason(res.Reason), - Variant: res.Variant, - }, - } -} - -// ... -``` - -## Provider lifecycle, initialization and shutdown - -With the release of the v0.6.0 spec, OpenFeature now outlines a lifecycle for in-process flagd provider initialization and shutdown. - -In-process flagd providers should do the following to make use of OpenFeature v0.6.0 features: - -- start in a `NOT_READY` state -- fetch the flag definition specified in the sync provider sources and set `state` to `READY` or `ERROR` in the `initialization` function - - note that the SDK will automatically emit `PROVIDER_READY`/`PROVIDER_ERROR` according to the termination of the `initialization` function -- throw an exception or terminate abnormally if a connection cannot be established during `initialization` -- For gRPC based sources (i.e. flagd-proxy), attempt to restore the streaming connection to flagd-proxy (if the connection cannot be established or is broken): - - If flag definition have been retrieved previously, go into `STALE` state to indicate that flag resolution responses are based on potentially outdated Flag definition. - - reconnection should be attempted with an exponential back-off, with a max-delay of `maxSyncRetryInterval` (see [configuration](#configuration)) - - reconnection should be attempted up to `maxSyncRetryDelay` times (see [configuration](#configuration)) - - `PROVIDER_READY` and `PROVIDER_CONFIGURATION_CHANGED` should be emitted, in that order, after successful reconnection -- For Kubernetes sync sources, retry to retrieve the FlagConfiguration resource, using an exponential back-off strategy, with a max-delay of `maxSyncRetryInterval` (see [configuration](#configuration)) -- emit `PROVIDER_CONFIGURATION_CHANGED` event and update the ruleset when a `configuration_change` message is received on the streaming connection -- close the streaming connection in the `shutdown` function - -```mermaid -stateDiagram-v2 - [*] --> NOT_READY - NOT_READY --> READY: initialize(), stream connected, flag configurations retrieved - NOT_READY --> ERROR: initialize(), unable to connect (retry) - READY --> STALE: previously retrieved flag configurations can not be retrieved anymore (emit stale*) - STALE --> READY: connection to flag source reestablished, and latest flag configurations retrieved (emit ready*, changed*) - STALE --> ERROR: connection reattempt failed after maxSyncRetries reached (emit error*) - READY --> READY: configuration_change (emit changed*) - ERROR --> READY: reconnect successful (emit ready*, changed*) - ERROR --> ERROR: maxSyncRetries reached - ERROR --> [*]: shutdown(), stream disconnected -``` - -\* ready=`PROVIDER_READY`, changed=`PROVIDER_CONFIGURATION_CHANGED`, stale=`PROVIDER_STALE`, error=`PROVIDER_ERROR` - -## Configuration - -Expose means to configure the provider aligned with the following priority system (highest to lowest). - -```mermaid -flowchart LR - constructor-parameters -->|highest priority| environment-variables -->|lowest priority| defaults -``` - -### Explicit declaration - -This takes the form of parameters to the provider's constructor, it has the highest priority. - -### Environment variables - -Read environment variables with sensible defaults (before applying the values explicitly declared to the constructor). - -| Option name | Environment variable name | Type | Options | Default | -| --------------------------- | ------------------------------------- | ------- | ------------ | -------------------------------------- | -| host | FLAGD_PROXY_HOST | string | | localhost | -| port | FLAGD_PROXY_PORT | number | | 8013 | -| tls | FLAGD_PROXY_TLS | boolean | | false | -| socketPath | FLAGD_PROXY_SOCKET_PATH | string | | | -| certPath | FLAGD_PROXY_SERVER_CERT_PATH | string | | | -| sourceURI | FLAGD_SOURCE_URI | string | | | -| sourceProviderType | FLAGD_SOURCE_PROVIDER_TYPE | string | | grpc | -| sourceSelector | FLAGD_SOURCE_SELECTOR | string | | | -| maxSyncRetries | FLAGD_MAX_SYNC_RETRIES | int | | 0 (0 means unlimited) | -| maxSyncRetryInterval | FLAGD_MAX_SYNC_RETRY_INTERVAL | int | | 60s | - -## Error handling - -Handle flag evaluation errors by using the error constructors exported by the SDK (e.g. `openfeature.NewProviderNotReadyResolutionError(ConnectionError)`), thereby allowing the SDK to parse and handle the error appropriately. - -## Post creation - -The following steps will extend the reach of the newly created provider to other developers of the chosen technology. - -### Open an issue to document the provider - -Create an issue [here](https://github.com/open-feature/openfeature.dev/issues/new?assignees=&labels=provider&template=document-provider.yaml&title=%5BProvider%5D%3A+) for adding the provider to [openfeature.dev](https://openfeature.dev). diff --git a/docs/reference/specifications/providers.md b/docs/reference/specifications/providers.md new file mode 100644 index 000000000..e17f9b51a --- /dev/null +++ b/docs/reference/specifications/providers.md @@ -0,0 +1,249 @@ +--- +description: flagd provider specification +--- + +!!! note + + This document serves as both a specification and general documentation for flagd providers. + For language-specific details, see the `README.md` for the provider in question. + +# flagd Providers + +flagd providers are as essential as the flagd daemon itself, acting as the "bridge" between the OpenFeature SDK and flagd. +In fact, flagd providers may be the most crucial part of the flagd framework, as they can be used without an active flagd instance. +This document outlines their behavior and configuration. + +## In-Process vs RPC Evaluation + +There are two modes of operation (`resolvers`) for flagd providers; _in-process_ and _RPC_. +Both modes have their advantages and disadvantages. +For more information on the architectural implications of these different modes, see the [RPC vs In-Process Evaluation](/architecture/#rpc-vs-in-process-evaluation) page. + +## Configuration + +### Configuration options + +Most options can be defined in the constructor, or as environment variables, with constructor options having the highest +precedence. + +Below are the supported configuration parameters (note that not all apply to both resolver modes): + +| Option name | Environment variable name | Explanation | Type & Values | Default | Compatible resolver | +| ------------------ | -------------------------- | ------------------------------------------------------------------------------- | ------------------------ | ----------------------------- | ------------------- | +| resolver | FLAGD_RESOLVER | mode of operation | String - rpc, in-process | rpc | rpc & in-process | +| host | FLAGD_HOST | remote host | String | localhost | rpc & in-process | +| port | FLAGD_PORT | remote port | int | 8013 (rpc), 8015 (in-process) | rpc & in-process | +| targetUri | FLAGD_GRPC_TARGET | alternative to host/port, supporting custom name resolution | string | null | rpc & in-process | +| tls | FLAGD_TLS | connection encryption | boolean | false | rpc & in-process | +| socketPath | FLAGD_SOCKET_PATH | alternative to host port, unix socket | String | null | rpc & in-process | +| certPath | FLAGD_SERVER_CERT_PATH | tls cert path | String | null | rpc & in-process | +| deadline | FLAGD_DEADLINE_MS | deadline for unary calls, and timeout for initialization | int | 500 | rpc & in-process | +| streamDeadlineMs | FLAGD_STREAM_DEADLINE_MS | deadline for streaming calls, useful as an application-layer keepalive | int | 600000 | rpc & in-process | +| retryBackoffMs | FLAGD_RETRY_BACKOFF_MS | initial backoff for stream retry | int | 1000 | rpc & in-process | +| retryBackoffMaxMs | FLAGD_RETRY_BACKOFF_MAX_MS | maximum backoff for stream retry | int | 120000 | rpc & in-process | +| retryGraceAttempts | FLAGD_RETRY_GRACE_ATTEMPTS | amount of stream retry attempts before provider moves from STALE to ERROR state | int | 5 | rpc & in-process | +| keepAliveTime | FLAGD_KEEP_ALIVE_TIME_MS | http 2 keepalive | long | 0 | rpc & in-process | +| cache | FLAGD_CACHE | enable cache of static flags | String - lru, disabled | lru | rpc | +| maxCacheSize | FLAGD_MAX_CACHE_SIZE | max size of static flag cache | int | 1000 | rpc | +| selector | FLAGD_SOURCE_SELECTOR | selects a single sync source to retrieve flags from only that source | string | null | in-process | +| contextEnricher | - | sync-metadata to evaluation context mapping function | string | identity function | in-process | + +### Custom Name Resolution + +Some implementations support [gRPC custom name resolution](https://grpc.io/docs/guides/custom-name-resolution/), and abstractions to introduce additional resolvers. +Specifically, a custom resolver for `envoy` has been implemented in some providers, which overrides the authority header with the authority specified in the envoy URL scheme. +Below is an example of a custom target string which will use envoy sidecar proxy for name resolution: + +```text +envoy://localhost:9211/flagd-sync.service +``` + +The custom name resolver provider in this case will use the endpoint name i.e. `flagd-sync.service` as [authority](https://github.com/grpc/grpc-java/blob/master/examples/src/main/java/io/grpc/examples/nameresolve/ExampleNameResolver.java#L55-L61) +and connect to `localhost:9211`. + +## flagd Provider Lifecycle + +flagd providers are built to adhere to the [provider lifecycle](https://openfeature.dev/specification/sections/flag-evaluation/#17-provider-lifecycle-management) defined in the OpenFeature specification. +Understanding the flagd provider lifecycle is helpful in configuring and optimizing your flagd deployment, and critical to implementing a flagd provider. + +The lifecycle is summarized below: + +- on initialization, attempt to connect the appropriate stream according to the resolver type (sync stream for in-process vs event stream for RPC) and in the case of in-process, fetch the sync-metadata + - if stream connection succeeds within the time specified by `deadline`, return from initialization (SDK will emit `PROVIDER_READY`) and for in-process providers, store the `flag set` rules + - if stream connection fails or exceeds the time specified by `deadline`, abort initialization (SDK will emit `PROVIDER_ERROR`), and attempt to [reconnect](#stream-reconnection) +- while connected: + - flags are resolved according to resolver mode; either by calling evaluation RPCs, or by evaluating the stored `flag set` rules + - for RPC providers, flags resolved with `reason=STATIC` are [cached](#caching) + - if flags change the associated stream (event or sync) indicates flags have changed, flush cache, or update `flag set` rules respectively and emit `PROVIDER_CONFIGURATION_CHANGED` +- if stream disconnects: + - attempt to [reconnect](#stream-reconnection) silently immediately + - if initial reconnect succeeds, do nothing + - if initial reconnect fails: + - emit `PROVIDER_ERROR` + - RPC providers invalidate cache + - reconnect with backoff +- on stream reconnection: + - emit `PROVIDER_READY` and `PROVIDER_CONFIGURATION_CHANGED` + - in-process providers store the latest `flag set` rules +- emit `PROVIDER_CONFIGURATION_CHANGED` event and update `flag set` rules when a `configuration_change` message is received on the streaming connection +- on shutdown, close the streaming connection in the`shutdown` function + +```mermaid +stateDiagram-v2 + [*] --> NOT_READY + NOT_READY --> READY: initialize + NOT_READY --> ERROR: initialize + READY --> ERROR: disconnected, reconnect attempt >= retry grace attempts + READY --> STALE: disconnected, reconnect attempt < retry grace attempts + STALE --> ERROR: reconnect attempt >= retry grace attempts + ERROR --> READY: reconnected + ERROR --> [*]: shutdown + + note right of STALE + stream disconnected, attempting to reconnect, + resolve from cache* + resolve from flag set rules** + STALE emitted + end note + + note right of READY + stream connected, + evaluation cache active*, + flag set rules stored**, + metadata fetched** + READY emitted + CHANGE emitted with stream messages + end note + + note right of ERROR + stream disconnected, attempting to reconnect, + evaluation cache invalidated*, + ERROR emitted + end note + +%% * RPC providers only +%% ** In-Process providers only +``` + +``` +* RPC providers only +** In-Process providers only +``` + +### Stream Reconnection + +When either stream (sync or event) disconnects, whether due to the associated deadline being exceeded, network error or any other cause, the provider attempts to re-establish the stream immediately, and then retries with an exponential back-off. +When disconnected, if the number of reconnect attempts is less than `retryGraceAttempts`, the provider emits `STALE`. +While the provider is in state `STALE` the provider resolves values from its cache or stored flag set rules, depending on its resolver mode. +When the number of reconnect attempts is equal to or greater than `retryGraceAttempts`, the provider emits `ERROR`. +The provider attempts to reconnect indefinitely, with a maximum interval of `retryBackoffMaxMs`. + +## RPC Providers + +RPC providers use the [evaluation protocol](./protos.md#flagdevaluationv1evaluationproto) to connect to flagd, initiate the [event stream](./protos.md#eventstreamresponse), listen for changes in the flag definitions, and evaluate flags remotely by calling flagd. +RPC providers are relatively simple to implement since they essentially call a remote flagd instance with relevant parameters, and then flagd responds with the resolved flag value. +Of course, this means there's latency associated with RPC providers, though this is mitigated somewhat by [caching](#flag-evaluation-caching). + +### Flag Evaluation Caching + +In RPC mode, `flagd` uses a caching mechanism which greatly reduces latency for static flags (flags without targeting rules). +Evaluations for flags with targeting rules are never cached. + +!!! note + + Evaluation caching is only relevant to when the RPC resolver is used; the in-process resolver stores a complete set of rules for a `flag set`, which means evaluation can be done locally, with low latency. + +#### Cacheable flags + +`flagd` sets the `reason` of a flag evaluation as `STATIC` when no targeting rules are configured for the flag. +A client can safely store the result of a static evaluation in its cache indefinitely (until the configuration of the flag changes, see [cache invalidation](#cache-invalidation)). + +Put simply in pseudocode: + +```pseudo +if reason == "STATIC" { + isFlagCacheable = true +} +``` + +#### Cache invalidation + +`flagd` emits events to the server-to-client stream, among these is the `configuration_change` event. +The structure of this event is as such: + +```json +{ + "type": "delete", // ENUM:["delete","write","update"] + "source": "/flag-configuration.json", // the source of the flag configuration + "flagKey": "foo" +} +``` + +A client should invalidate the cache of any flag found in a `configuration_change` event to prevent stale data. +If the connection drops all cache values must be cleared (any number of events may have been missed). + +### Client Side Providers + +Client side flagd providers (used in mobile and front-end web applications) have unique security and performance considerations. +These flagd providers only support the RPC resolver mode (so that `flag set` rules, which might contain sensitive information, are never sent to the client). +Instead, these do bulk evaluations of all flags in the `flag set`, and cache the results until they are invalidated. +Bulk evaluations take place when: +- the provider is initialized +- the context is changed +- a change in the definition notifies the provider it should re-evaluate the flags + +This pattern is consistent with OpenFeature's [static context paradigm](https://openfeature.dev/specification/glossary#static-context-paradigm). + +!!! note + + To support easy integration with mobile and browser use cases, flagd's [evaluation protocol](./protos.md#flagdevaluationv1evaluationproto) is accessible over both gRPC and HTTP + +!!! note + + flagd supports the OFREP protocol, meaning client-side OFREP providers can also be used for client-side use-cases. + +### Provider Metadata + +The provider metadata includes properties returned from the [provider_ready event payload](./protos.md#eventstreamresponse) data. + +## In-Process Providers + +In-process providers use the [sync schema](./protos.md#syncflagsresponse) to connect to flagd, initiate the [sync stream](./protos.md#eventstreamresponse), and download the `flag-set` rules to evaluate them locally. +In-process providers are relatively complex (compared to RPC providers) to implement since they essentially must implement more of flagd's logic to evaluate flags locally. +Local evaluation has the impact of much lower latency and almost no serialization compared to RPC providers. + +### JsonLogic Evaluation + +An in-process flagd providers provide the feature set offered by [JsonLogic](https://jsonlogic.com) to evaluate flag resolution requests for a given context. + +### Custom JsonLogic Evaluators + +In addition to the built-in evaluators provided by JsonLogic, the following custom targeting rules are implemented by the provider: + +- [Fractional operation](../../reference/custom-operations/fractional-operation.md) +- [Semantic version evaluation](../../reference/custom-operations/semver-operation.md) +- [StartsWith/EndsWith evaluation](../../reference/custom-operations/string-comparison-operation.md) + +### Targeting Key + +Similar to the flagd daemon, in-process providers map the [targeting-key](https://openfeature.dev/specification/glossary#targeting-key) into a top level property of the context used in rules, with the key `"targetingKey"`. + +### `$flagd` Properties in the Evaluation Context + +Similar to the flagd daemon, in-process flagd providers add the following properties to the JsonLogic evaluation context so that users can use them in their targeting rules. +Conflicting properties in the context will be overwritten by the values below. + +| Property | Description | +| ------------------ | ------------------------------------------------------- | +| `$flagd.flagKey` | the identifier for the flag being evaluated | +| `$flagd.timestamp` | a unix timestamp (in seconds) of the time of evaluation | + +### Sync-Metadata Properties in the Evaluation Context + +In-process flagd providers also inject any properties returned by the [sync-metadata RPC response](./protos.md#getmetadataresponse) into the context. +This allows for static properties defined in flagd to be added to in-process evaluations. +If only a subset of the sync-metadata response is desired to be injected into the evaluation context, you can use the define a mapping function with the `contextEnricher` option. + +### Provider Metadata + +The provider metadata includes the top-level metadata property in the [flag definition](../flag-definitions.md). \ No newline at end of file diff --git a/docs/reference/specifications/rpc-providers.md b/docs/reference/specifications/rpc-providers.md deleted file mode 100644 index fc220b2e5..000000000 --- a/docs/reference/specifications/rpc-providers.md +++ /dev/null @@ -1,164 +0,0 @@ ---- -description: flagd RPC provider specification ---- - -# Creating an RPC flagd provider - -By default, **flagd** is a remote service that is accessed via **grpc** by a client application to retrieve feature flags. -Depending on the environment, flagd therefore is usually deployed as a standalone service, e.g. as a Kubernetes Deployment, -or injected as a sidecar container into the pod running the client application, -as it is done in the [OpenFeature Operator](https://github.com/open-feature/open-feature-operator). - -Prerequisites: - -- Understanding of [general provider concepts](https://openfeature.dev/docs/reference/concepts/provider/) -- Proficiency in the chosen programming language (check the language isn't already covered by the [existing providers](../../providers/index.md)) - -## flagd Evaluation API - -Fundamentally, RPC providers use the [evaluation schema](./protos.md#schemav1schemaproto) to connect to flagd, initiate evaluation RPCs, and listen for changes in the flag definitions. -In order to do this, you must generate the gRPC primitives (message types and client) using the protobuf code generation mechanisms available in your language. -If you are unable to use gRPC code generation, you can also use REST (via the [connect protocol](https://buf.build/blog/connect-a-better-grpc)) to communicate with flagd, though in this case, you will not be able to open a stream to listen for changes. - -### Protobuf - -Protobuf schemas define the contract between the flagd evaluation API and a client. - -#### Code generation for gRPC sync - -Leverage the [buf CLI](https://docs.buf.build/installation) or protoc to generate a `flagd-proxy` client in the chosen technology: - -Add the [open-feature schema repository](https://github.com/open-feature/schemas) as a submodule - -```shell -git submodule add --force https://github.com/open-feature/schemas.git -``` - -Create a `buf.gen.{chosen language}.yaml` for the chosen language in `schemas/protobuf` (if it doesn't already exist) using one of the other files as a template (find a plugin for the chosen language [here](https://buf.build/protocolbuffers/plugins)) and create a pull request with this file. - -Generate the code (this step ought to be automated in the build process for the chosen technology so that the generated code is never committed) - -```shell -cd schemas/protobuf -buf generate --template buf.gen.{chosen language}.yaml -``` - -As an alternative to buf, use the .proto file directly along with whatever protoc-related tools or plugins available for your language. - -Move the generated code (following convention for the chosen language) and add its location to .gitignore - -Note that for the in-process provider only the `schema` package will be relevant, since RPC providers communicate directly to flagd. - -## Provider lifecycle, initialization and shutdown - -With the release of the v0.6.0 spec, OpenFeature now outlines a lifecycle for in-process flagd provider initialization and shutdown. - -In-process flagd providers should do the following to make use of OpenFeature v0.6.0 features: - -- start in a `NOT_READY` state -- fetch the flag definition specified in the sync provider sources and set `state` to `READY` or `ERROR` in the `initialization` function - - note that the SDK will automatically emit `PROVIDER_READY`/`PROVIDER_ERROR` according to the termination of the `initialization` function -- throw an exception or terminate abnormally if a connection cannot be established during `initialization` -- For gRPC based sources (i.e. flagd-proxy), attempt to restore the streaming connection to flagd-proxy (if the connection cannot be established or is broken): - - If flag definition have been retrieved previously, go into `STALE` state to indicate that flag resolution responses are based on potentially outdated Flag definition. - - reconnection should be attempted with an exponential back-off, with a max-delay of `maxSyncRetryInterval` (see [configuration](#configuration)) - - reconnection should be attempted up to `maxSyncRetryDelay` times (see [configuration](#configuration)) - - `PROVIDER_READY` and `PROVIDER_CONFIGURATION_CHANGED` should be emitted, in that order, after successful reconnection -- For Kubernetes sync sources, retry to retrieve the FlagConfiguration resource, using an exponential back-off strategy, with a max-delay of `maxSyncRetryInterval` (see [configuration](#configuration)) -- emit `PROVIDER_CONFIGURATION_CHANGED` event and update ruleset when a `configuration_change` message is received on the streaming connection -- close the streaming connection in the`shutdown` function - -```mermaid -stateDiagram-v2 - [*] --> NOT_READY - NOT_READY --> READY: initialize(), connection to flagd established, stream connected - NOT_READY --> ERROR: initialize(), unable to connect or establish stream(retry) - READY --> ERROR: stream or connection disconnected - READY --> READY: configuration_change (emit changed*, invalidate cache) - ERROR --> READY: reconnect successful (emit ready*, changed*, invalidate cache) - ERROR --> ERROR: maxSyncRetries reached - ERROR --> [*]: shutdown(), stream disconnected -``` - -\* ready=`PROVIDER_READY`, changed=`PROVIDER_CONFIGURATION_CHANGED`, stale=`PROVIDER_STALE`, error=`PROVIDER_ERROR` - -## Caching - -`flagd` has a caching strategy implementable by RPC providers that support server-to-client streaming. - -### Cacheable flags - -`flagd` sets the `reason` of a flag evaluation as `STATIC` when no targeting rules are configured for the flag. -A client can safely store the result of a static evaluation in its cache indefinitely (until the configuration of the flag changes, see [cache invalidation](#cache-invalidation)). - -Put simply in pseudocode: - -```pseudo -if reason == "STATIC" { - isFlagCacheable = true -} -``` - -### Cache invalidation - -`flagd` emits events to the server-to-client stream, among these is the `configuration_change` event. -The structure of this event is as such: - -```json -{ - "type": "delete", // ENUM:["delete","write","update"] - "source": "/flag-configuration.json", // the source of the flag configuration - "flagKey": "foo" -} -``` - -A client should invalidate the cache of any flag found in a `configuration_change` event to prevent stale data. -If the connection drops all cache values must be cleared (any number of events may have been missed). - -## Configuration - -Expose means to configure the provider aligned with the following priority system (highest to lowest). - -```mermaid -flowchart LR - constructor-parameters -->|highest priority| environment-variables -->|lowest priority| defaults -``` - -### Explicit declaration - -This takes the form of parameters to the provider's constructor, it has the highest priority. - -### Environment variables - -Read environment variables with sensible defaults (before applying the values explicitly declared to the constructor). - -| Option name | Environment variable name | Type & Values | Default | -|-----------------------|--------------------------------|------------------------|-----------| -| host | FLAGD_HOST | String | localhost | -| port | FLAGD_PORT | int | 8013 | -| tls | FLAGD_TLS | boolean | false | -| socketPath | FLAGD_SOCKET_PATH | String | null | -| certPath | FLAGD_SERVER_CERT_PATH | String | null | -| deadline | FLAGD_DEADLINE_MS | int | 500 | -| cache | FLAGD_CACHE | String - lru, disabled | lru | -| maxCacheSize | FLAGD_MAX_CACHE_SIZE | int | 1000 | -| maxEventStreamRetries | FLAGD_MAX_EVENT_STREAM_RETRIES | int | 5 | -| retryBackoffMs | FLAGD_RETRY_BACKOFF_MS | int | 1000 | - -## Error handling - -Handle flag evaluation errors by using the error constructors exported by the SDK (e.g. `openfeature.NewProviderNotReadyResolutionError(ConnectionError)`), thereby allowing the SDK to parse and handle the error appropriately. - -## Post creation - -The following steps will extend the reach of the newly created provider to other developers of the chosen technology. - -### Open an issue to document the provider - -Create an issue in openfeature.dev [here](https://github.com/open-feature/openfeature.dev/issues/new?assignees=&labels=provider&template=document-provider.yaml&title=%5BProvider%5D%3A+). -This will ensure the provider is added to OpenFeature's website. - -## Serialization of the evaluation context - -An RPC provider should serialize the OpenFeature context for use in the `evaluation.proto`. -It should map the [targeting-key](https://openfeature.dev/specification/glossary#targeting-key) into a top level property of the context, with the key `"targetingKey"` diff --git a/mkdocs.yml b/mkdocs.yml index 9e38210eb..4d1e1ca6b 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -100,8 +100,7 @@ nav: - 'Schema': 'reference/schema.md' - 'Monitoring': 'reference/monitoring.md' - 'Specifications': - - 'RPC Providers': 'reference/specifications/rpc-providers.md' - - 'In-Process Providers': 'reference/specifications/in-process-providers.md' + - 'Providers': 'reference/specifications/providers.md' - 'Protobuf Schemas': 'reference/specifications/protos.md' - 'Custom Operations': - 'Fractional Specification': 'reference/specifications/custom-operations/fractional-operation-spec.md' @@ -124,3 +123,5 @@ plugins: 'reference/openfeature-operator/installation.md': 'https://github.com/open-feature/open-feature-operator/blob/main/docs/installation.md' 'reference/openfeature-operator/crds/featureflag.md': 'https://github.com/open-feature/open-feature-operator/blob/main/docs/feature_flag.md' 'reference/openfeature-operator/crds/featureflagsource.md': https://github.com/open-feature/open-feature-operator/blob/main/docs/feature_flag_source.md + 'reference/specifications/rpc-providers.md': 'reference/specifications/providers.md#rpc-providers' + 'reference/specifications/in-process-providers.md': 'reference/specifications/providers.md#in-process-providers'