From 04122ad3b9f72212f027591655a2eb19d9fe178d Mon Sep 17 00:00:00 2001 From: Skye Gill Date: Wed, 15 Feb 2023 11:46:09 +0000 Subject: [PATCH 1/6] docs: documentation for creating a provider Signed-off-by: Skye Gill --- docs/other_resources/caching.md | 3 +- docs/other_resources/creating_providers.md | 126 +++++++++++++++++++++ docs/usage/flagd_providers.md | 4 +- 3 files changed, 130 insertions(+), 3 deletions(-) create mode 100644 docs/other_resources/creating_providers.md diff --git a/docs/other_resources/caching.md b/docs/other_resources/caching.md index 7c2bd4552..0599db3b7 100644 --- a/docs/other_resources/caching.md +++ b/docs/other_resources/caching.md @@ -26,4 +26,5 @@ if reason == "STATIC" { } ``` -A client should bust the cache of any flag found in a `configuration_change` event to prevent stale data. +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). diff --git a/docs/other_resources/creating_providers.md b/docs/other_resources/creating_providers.md new file mode 100644 index 000000000..b0fd0779e --- /dev/null +++ b/docs/other_resources/creating_providers.md @@ -0,0 +1,126 @@ +# Creating a flagd provider + +The provider is responsible for creating an abstraction between `flagd` and the OpenFeature SDK (for the [chosen technology](https://docs.openfeature.dev/docs/reference/technologies/)). + +Prerequisites: + +- Understanding of [general provider concepts](https://docs.openfeature.dev/docs/reference/concepts/provider/) +- Proficiency in the chosen programming language (check the language isn't already covered by the [existing providers](../usage/flagd_providers.md)) + +Communication with `flagd` is possible via Protobuf or REST. Protobuf is the recommended approach (as such, that is the approach outlined in this document) when possible (not possible if no tooling exists for a given technology). + +## Protobuf + +Protobuf schemas define the contract between a client (provider) and server (`flagd`). `flagd`'s schemas are defined [here](https://github.com/open-feature/schemas/tree/main/protobuf). + +### Code generation + +Leverage the [buf CLI](https://docs.buf.build/installation) to generate a `flagd` client in the chosen technology: + +Add the [open-feature schema repository](https://github.com/open-feature/schemas) as a submodule +``` +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 your 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) +``` +cd schemas/protobuf +buf generate --template buf.gen.{chosen language}.yaml +``` + +Move the generated code (following convention for the chosen language) and add its location to .gitignore + +## 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 unexported (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 { + client generated.Client + flagdHost string +} + +type ProviderOption func(*Provider) + +func NewProvider(options ...ProviderOption) *Provider { + provider := &Provider{} + for _, opt := range opts { + opt(provider) + } + + provider.client: generated.NewClient(provider.flagdHost), + + 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.client.ResolveBoolean(ctx, connect.NewRequest(&schemaV1.ResolveBooleanRequest{ + FlagKey: flagKey, + // omitted Context for simplicity, see Go's provider for completeness + })) + if err != nil { + return of.BoolResolutionDetail{ + Value: defaultValue, + ProviderResolutionDetail: of.ProviderResolutionDetail{ + ResolutionError: e, + Reason: of.Reason(res.Reason), + Variant: res.Variant, + }, + } + } + + return of.BoolResolutionDetail{ + Value: defaultValue, + ProviderResolutionDetail: of.ProviderResolutionDetail{ + ResolutionError: e, + Reason: of.Reason(res.Reason), + Variant: res.Variant, + }, + } +} + +// ... +``` + +## Caching + +Follow the guidance [here](./caching.md) to implement caching. + +## Configuration + +Expose means to configure the provider. + +### 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). + +e.g. + +| Option name | Environment variable name | Type | Default | +|-----------------|---------------------------| ------- |-----------| +| host | FLAGD_HOST | string | localhost | +| port | FLAGD_PORT | number | 8013 | +| tls | FLAGD_TLS | boolean | false | +| socketPath | FLAGD_SOCKET_PATH | string | | +| certPath | FLAGD_SERVER_CERT_PATH | string | | +| cachingDisabled | FLAGD_CACHING_DISABLED | boolean | false | + +## 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. diff --git a/docs/usage/flagd_providers.md b/docs/usage/flagd_providers.md index 55085f918..901670b96 100644 --- a/docs/usage/flagd_providers.md +++ b/docs/usage/flagd_providers.md @@ -1,6 +1,6 @@ # Flagd Providers -Flagd providers are used for interacting with the flagd service via the OpenFeature SDK, they act as the translation layer between the evaluation API and the flag management system in use (in this case flagd). Documentation for each language specific provider can be found below: +Flagd providers are used for interacting with the `flagd` service via the OpenFeature SDK, they act as the translation layer between the evaluation API and the flag management system in use (in this case `flagd`). Documentation for each language specific provider can be found below: | Language | Provider | | ----------- | ----------- | @@ -12,4 +12,4 @@ Flagd providers are used for interacting with the flagd service via the OpenFeat | .NET | Not currently available, [help by contributing here](https://github.com/open-feature/dotnet-sdk-contrib) | Ruby | Not currently available, [help by contributing here](https://github.com/open-feature/ruby-sdk-contrib) - +Any (new or existing) `flagd` providers ought to follow [these guidelines](../other_resources/creating_providers.md). From 6975cd1a8e0beb388e70e5eb55d979e979c5a37a Mon Sep 17 00:00:00 2001 From: Skye Gill Date: Wed, 15 Feb 2023 11:47:47 +0000 Subject: [PATCH 2/6] doc fix Signed-off-by: Skye Gill --- docs/other_resources/creating_providers.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/other_resources/creating_providers.md b/docs/other_resources/creating_providers.md index b0fd0779e..8fd86ac20 100644 --- a/docs/other_resources/creating_providers.md +++ b/docs/other_resources/creating_providers.md @@ -21,7 +21,7 @@ Add the [open-feature schema repository](https://github.com/open-feature/schemas ``` 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 your chosen language [here](https://buf.build/protocolbuffers/plugins)) and create a pull request with this file. +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) ``` From 9658c1d03770ffc32330ba38425eb8d73c560063 Mon Sep 17 00:00:00 2001 From: Skye Gill Date: Wed, 15 Feb 2023 17:23:47 +0000 Subject: [PATCH 3/6] cr suggestions Signed-off-by: Skye Gill --- docs/other_resources/creating_providers.md | 38 +++++++++++++++------- 1 file changed, 27 insertions(+), 11 deletions(-) diff --git a/docs/other_resources/creating_providers.md b/docs/other_resources/creating_providers.md index 8fd86ac20..c882fe6ff 100644 --- a/docs/other_resources/creating_providers.md +++ b/docs/other_resources/creating_providers.md @@ -100,7 +100,11 @@ Follow the guidance [here](./caching.md) to implement caching. ## Configuration -Expose means to configure the provider. +Expose means to configure the provider aligned with the following priority system (highest to lowest). + +```mermaid + constructor-parameters -->|highest priority| environment-variables -->|lowest priority| defaults +``` ### Explicit declaration @@ -110,17 +114,29 @@ This takes the form of parameters to the provider's constructor, it has the high Read environment variables with sensible defaults (before applying the values explicitly declared to the constructor). -e.g. - -| Option name | Environment variable name | Type | Default | -|-----------------|---------------------------| ------- |-----------| -| host | FLAGD_HOST | string | localhost | -| port | FLAGD_PORT | number | 8013 | -| tls | FLAGD_TLS | boolean | false | -| socketPath | FLAGD_SOCKET_PATH | string | | -| certPath | FLAGD_SERVER_CERT_PATH | string | | -| cachingDisabled | FLAGD_CACHING_DISABLED | boolean | false | +| Option name | Environment variable name | Type | Options | Default | +|-----------------------|--------------------------------|-----------|--------------|----------------------------------------| +| host | FLAGD_HOST | string | | localhost | +| port | FLAGD_PORT | number | | 8013 | +| tls | FLAGD_TLS | boolean | | false | +| socketPath | FLAGD_SOCKET_PATH | string | | | +| certPath | FLAGD_SERVER_CERT_PATH | string | | | +| cache | FLAGD_CACHE | string | lru,disabled | lru (if possible in chosen technology) | +| maxCacheSize | FLAGD_MAX_CACHE_SIZE | int | | 1000 | +| maxEventStreamRetries | FLAGD_MAX_EVENT_STREAM_RETRIES | int | | 5 | ## 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. + +### Add to flagd's list of providers + +Create a pull request appending the provider to the list [here](../usage/flagd_providers.md). + +### Open an issue to document the provider + +Create an issue in docs.openfeature.dev [here](https://github.com/open-feature/docs.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. From 27f8a77064eafb23a898b8047163289b4ae24af1 Mon Sep 17 00:00:00 2001 From: Michael Beemer Date: Wed, 15 Feb 2023 12:47:57 -0500 Subject: [PATCH 4/6] fix mermaid chart Signed-off-by: Michael Beemer --- docs/other_resources/creating_providers.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/other_resources/creating_providers.md b/docs/other_resources/creating_providers.md index c882fe6ff..db0309b8e 100644 --- a/docs/other_resources/creating_providers.md +++ b/docs/other_resources/creating_providers.md @@ -103,6 +103,7 @@ Follow the guidance [here](./caching.md) to implement caching. 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 ``` From 5498759adf83c7e4c1a1970d6812943f07b44f2b Mon Sep 17 00:00:00 2001 From: Michael Beemer Date: Wed, 15 Feb 2023 13:03:20 -0500 Subject: [PATCH 5/6] add create providers entry on doc landing page Signed-off-by: Michael Beemer --- docs/README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/README.md b/docs/README.md index 364712608..4ba15e7cd 100644 --- a/docs/README.md +++ b/docs/README.md @@ -27,7 +27,9 @@ This section documents any behavior of flagd which may seem unexpected, currentl - [Omitted value from evaluation response](./help/omitted_value_from_response.md) ## Other Resources + - [High level architecture](./other_resources/high_level_architecture.md) +- [Creating providers](./other_resources/creating_providers.md) - [Caching](./other_resources/caching.md) - [Snap](./other_resources/snap.md) - [Systemd service](./other_resources/systemd_service.md) From 97f6b6d3afbb7062e522286055e4725d3b2a40fa Mon Sep 17 00:00:00 2001 From: Skye Gill Date: Thu, 16 Feb 2023 14:06:16 +0000 Subject: [PATCH 6/6] fix code example Signed-off-by: Skye Gill --- docs/other_resources/creating_providers.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/docs/other_resources/creating_providers.md b/docs/other_resources/creating_providers.md index db0309b8e..51a761900 100644 --- a/docs/other_resources/creating_providers.md +++ b/docs/other_resources/creating_providers.md @@ -74,7 +74,7 @@ func (p *Provider) BooleanEvaluation( return of.BoolResolutionDetail{ Value: defaultValue, ProviderResolutionDetail: of.ProviderResolutionDetail{ - ResolutionError: e, + ResolutionError: of.NewGeneralResolutionError(err.Error()), Reason: of.Reason(res.Reason), Variant: res.Variant, }, @@ -84,7 +84,6 @@ func (p *Provider) BooleanEvaluation( return of.BoolResolutionDetail{ Value: defaultValue, ProviderResolutionDetail: of.ProviderResolutionDetail{ - ResolutionError: e, Reason: of.Reason(res.Reason), Variant: res.Variant, },