From 068a9a5dab0e8ff3addbef38b01bdda91fbc52bf Mon Sep 17 00:00:00 2001 From: Thomas Poignant Date: Wed, 27 Nov 2024 22:54:59 +0100 Subject: [PATCH 1/8] doc(specification): Flag set specification proposal Signed-off-by: Thomas Poignant --- .../pages/specification/20241027-flagsets.md | 200 ++++++++++++++++++ 1 file changed, 200 insertions(+) create mode 100644 website/src/pages/specification/20241027-flagsets.md diff --git a/website/src/pages/specification/20241027-flagsets.md b/website/src/pages/specification/20241027-flagsets.md new file mode 100644 index 00000000000..6809ca3794c --- /dev/null +++ b/website/src/pages/specification/20241027-flagsets.md @@ -0,0 +1,200 @@ +--- +title: Flag sets +Description: Flag sets are a way to group flags together. +--- + +# Flag sets + +| | | +|----------------------|---------------------------------------------------| +| **Creation Date** | 27/10/2024 | +| **Last Update Date** | 27/10/2024 | +| **Authors** | Thomas Poignant | +| **Status** | ![draft](https://img.shields.io/badge/-draft-red) | + +## Definition + +> A collection of related flags. This grouping helps organize feature flags based on their intended use, facilitating easier management and deployment. + +_Source: [Openfeature glossary](https://openfeature.dev/specification/glossary/#flag-set)._ + + +## Context + +GO Feature Flag is supporting both flag evaluation for client and server-side. +While the server side evaluations are evaluating flags 1 by 1, the client-side providers are evaluating flags in bulk. +The main reason why client are evaluating flags in bulk is because in the client-side the evaluation context is not changing +for every evaluation, so in order to limit the number of requests to the relay proxy, we are evaluating flags in bulk and keeping +them in memory inside the different client providers _(`web`, `iOS` and `android`)_ for OpenFeature. + +### Sequence diagram of a client-side evaluation + +```mermaid +sequenceDiagram + participant app as Application + participant Client as Openfeature SDK + participant Provider as GO Feature Flag Provider + participant RelayProxy + app->>Client: SetContext(myEvaluationCtx) + Client->>Provider: init() + Provider->>RelayProxy: call /ofrep/v1/evaluate/flags + RelayProxy-->>Provider: return all evaluation responses + Provider->>Provider: Cache evaluation responses + Provider-->>Client: + Client -->> app: + + app->>Client: getBooleanValue(...) + Client->>Provider: resolveBooleanValue(...) + Provider-->>Client: evaluation response from cache for the flag + Client -->> app: flag value +``` +:::note +For simplicity, we are not showing the cache mechanism in the sequence diagram, and how the cache is updated in case of flag configuration changes. +::: + +### Why introducing flag sets? + +As of today, in the client-side paradigm, we are evaluating all the flags available in GO Feature Flag based on the received evaluation context. +This means that we are evaluating all the flags available in the project, but in some cases, we might want to evaluate only a subset of the flags. + +**When do we want to use flag sets?** +- We have multiple teams using the same GO Feature Flag instance, and we want to separate the flags evaluated by each team. +- We have different platforms using GO Feature Flag, and we want to limit which flags are evaluated by each platform. +- We want to give access to a list of flags to a specific user group. +- We want to allow 2 flags with the same name if they are used by different teams or platforms. + +**For all those points, as of today the only way to achieve this is to run multiple instances of GO Feature Flag, which is not ideal.** + +## Requirements + +- A flag can be part of only 1 flag set. +- Flag name are unique within a flag set, but not across flag sets. +- GO Feature Flag should have a `default` flag set for users not using the flag sets feature _(the behaviours should be exactly the same as of today if the feature is not used)_. +- When calling the bulk evaluation APIs (`/ofrep/v1/evaluate/flags` or `/v1/allflags`), we will determine which flag set to evaluate based on API key used. +- The bulk evaluation APIs should evaluate only 1 flag set at a time _(to avoid collision in flag names)_. +- It should be able to specify which flag set to evaluate when calling the evaluation APIs. _(e.g. `/ofrep/v1/evaluate/flags` or `/ofrep/v1/evaluate/flags/{flag_key}`)_. + - Ideally we should be able to know which flag set to used based on the API Key used. + - If GOFF is configured to be public, we should be able to specify the flag set to evaluate in the request _(with a specific header ex:`goff-flag-set`)_. + - In the providers, we should be able to specify the flag set to evaluate directly in the constructor _(by providing an API Key, or the name of the flag set)_. +- Admin API Keys, should be able to evaluate all the flag sets _(to be able to see all the flags available in the project)_. If none specified in the request, the default flag set should be evaluated. + + +## Out of scope for now +- Dynamic flag sets based on the evaluation context _([as mentioned in this slack message](https://gophers.slack.com/archives/C029TH8KDFG/p1732703075509229))_. +- Single retrievers for multiple flag sets _(as proposed in https://github.com/thomaspoignant/go-feature-flag/issues/2314)_. + +_Even if out of scope for now, those are interesting options that we may want to implement later._ + +## Proposed solution + +### Solution 1: 1 flag set per file + +In this solution we consider that we need at least one file per flag set. +All the flags retrieved by a `retriever` _(aka in the same file)_ will be associated to the same flag set. + +We can specify the flag set name in the configuration file. +As of today, we can still have multiple files for 1 flag set _(by specifying the same `flagSet` name in each file)_, +and we will have the same mechanism as of today _(with flag overridden in case of flag name collision)_. + +#### Flags configuration file +```yaml +# config-file1.goff.yaml +# Syntax used in this example is just for the sake of the example, it is not the final syntax. +flagSet: flagset-teamA + +featureA-enabled: + variations: + enabled: true + disabled: false + defaultRule: + variation: enabled +``` +```yaml +# config-file2.goff.yaml +flagSet: flagset-teamB + +featureA-enabled: + variations: + enabled: true + disabled: false + defaultRule: + variation: enabled +``` + +#### Relay-proxy configuration example +```yaml +# ... +retrievers: + - kind: file + path: config-file1.goff.yaml + - kind: file + path: config-file2.goff.yaml + +authorizedKeys: + evaluation: + - apikey1 # owner: userID1 + - apikey2 # owner: userID2 + admin: + - apikey3 +``` + +**PRO** +- It is simple to configure a flag set by putting all the flags at the same place. +- It is easy to understand which flags are part of a flag set. +- It is easy to give ownership of a flag set to a team, by giving them access to the file. + +**CON** +- If we want the same flag to be part of multiple flag sets, we need to duplicate the flag in multiple files. + +### Solution 2: specify the flag set in the retriever configuration + +In this solution we consider that we need at least one file per flag set. +All the flags retrieved by a `retriever` _(aka in the same file)_ will be associated to the same flag set. + +We associate the flag set to the retriever in the configuration, +#### Flags configuration file +```yaml +# config-file1.goff.yaml +featureA-enabled: + variations: + enabled: true + disabled: false + defaultRule: + variation: enabled +``` +```yaml +# config-file2.goff.yaml +featureA-enabled: + variations: + enabled: true + disabled: false + defaultRule: + variation: enabled +``` + +#### Relay-proxy configuration example +```yaml +# ... +retrievers: + - kind: file + path: config-file1.goff.yaml + flagSet: flagset-teamA + - kind: file + path: config-file2.goff.yaml + flagSet: flagset-teamB + +authorizedKeys: + evaluation: + - apikey1 # owner: userID1 + - apikey2 # owner: userID2 + admin: + - apikey3 +``` +### Solution 3 +:::note +Feel free to propose other solutions here. +::: + +## Decision + +## Consequences From 185aee7c6593cc5d7c2a6404c126d19435b88786 Mon Sep 17 00:00:00 2001 From: Thomas Poignant Date: Wed, 27 Nov 2024 23:15:13 +0100 Subject: [PATCH 2/8] adding pro/con solution 2 Signed-off-by: Thomas Poignant --- .../src/pages/specification/20241027-flagsets.md | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/website/src/pages/specification/20241027-flagsets.md b/website/src/pages/specification/20241027-flagsets.md index 6809ca3794c..0313b0ec677 100644 --- a/website/src/pages/specification/20241027-flagsets.md +++ b/website/src/pages/specification/20241027-flagsets.md @@ -14,9 +14,8 @@ Description: Flag sets are a way to group flags together. ## Definition -> A collection of related flags. This grouping helps organize feature flags based on their intended use, facilitating easier management and deployment. - -_Source: [Openfeature glossary](https://openfeature.dev/specification/glossary/#flag-set)._ +> A collection of related flags. This grouping helps organize feature flags based on their intended use, facilitating easier management and deployment. +> _Source: [Openfeature glossary](https://openfeature.dev/specification/glossary/#flag-set)._ ## Context @@ -144,6 +143,7 @@ authorizedKeys: - It is easy to give ownership of a flag set to a team, by giving them access to the file. **CON** +- The flag set configuration is at the same place as the flag configuration, so it is possible to edit the flag set name directly inside the file _(risk of overriding another flag set)_. - If we want the same flag to be part of multiple flag sets, we need to duplicate the flag in multiple files. ### Solution 2: specify the flag set in the retriever configuration @@ -190,6 +190,16 @@ authorizedKeys: admin: - apikey3 ``` + +**PRO** +- It is simple to configure a flag set by putting all the flags at the same place. +- It is easy to understand which flags are part of a flag set. +- It is easy to give ownership of a flag set to a team, by giving them access to the file. +- The flag set name is directly associated to the retriever, so it is not possible to edit it directly inside the file. + +**CON** +- If we want the same flag to be part of multiple flag sets, we need to duplicate the flag in multiple files. + ### Solution 3 :::note Feel free to propose other solutions here. From 8857edab5bdea3f781bfb0ad372e6028400ac331 Mon Sep 17 00:00:00 2001 From: Thomas Poignant Date: Thu, 28 Nov 2024 00:02:37 +0100 Subject: [PATCH 3/8] rework introduction to be simpler Signed-off-by: Thomas Poignant --- .../pages/specification/20241027-flagsets.md | 40 +++---------------- 1 file changed, 6 insertions(+), 34 deletions(-) diff --git a/website/src/pages/specification/20241027-flagsets.md b/website/src/pages/specification/20241027-flagsets.md index 0313b0ec677..684be012b27 100644 --- a/website/src/pages/specification/20241027-flagsets.md +++ b/website/src/pages/specification/20241027-flagsets.md @@ -20,47 +20,19 @@ Description: Flag sets are a way to group flags together. ## Context -GO Feature Flag is supporting both flag evaluation for client and server-side. -While the server side evaluations are evaluating flags 1 by 1, the client-side providers are evaluating flags in bulk. -The main reason why client are evaluating flags in bulk is because in the client-side the evaluation context is not changing -for every evaluation, so in order to limit the number of requests to the relay proxy, we are evaluating flags in bulk and keeping -them in memory inside the different client providers _(`web`, `iOS` and `android`)_ for OpenFeature. - -### Sequence diagram of a client-side evaluation - -```mermaid -sequenceDiagram - participant app as Application - participant Client as Openfeature SDK - participant Provider as GO Feature Flag Provider - participant RelayProxy - app->>Client: SetContext(myEvaluationCtx) - Client->>Provider: init() - Provider->>RelayProxy: call /ofrep/v1/evaluate/flags - RelayProxy-->>Provider: return all evaluation responses - Provider->>Provider: Cache evaluation responses - Provider-->>Client: - Client -->> app: - - app->>Client: getBooleanValue(...) - Client->>Provider: resolveBooleanValue(...) - Provider-->>Client: evaluation response from cache for the flag - Client -->> app: flag value -``` -:::note -For simplicity, we are not showing the cache mechanism in the sequence diagram, and how the cache is updated in case of flag configuration changes. -::: +As of today, GO Feature Flag is evaluating flag differently for client and server evaluations. -### Why introducing flag sets? +- In the **client-side**, we are evaluating the flags in bulk, and we are keeping the evaluation responses in memory for the duration of the application. +- In the **server-side**, we are evaluating the flags 1 by 1, and we are not keeping the evaluation responses in memory _(except if a cache is configured)_. -As of today, in the client-side paradigm, we are evaluating all the flags available in GO Feature Flag based on the received evaluation context. -This means that we are evaluating all the flags available in the project, but in some cases, we might want to evaluate only a subset of the flags. +In both cases having a flag set could be beneficial, for **performance reasons**, but also for **organization reasons**. -**When do we want to use flag sets?** +### When do we want to use flag sets? - We have multiple teams using the same GO Feature Flag instance, and we want to separate the flags evaluated by each team. - We have different platforms using GO Feature Flag, and we want to limit which flags are evaluated by each platform. - We want to give access to a list of flags to a specific user group. - We want to allow 2 flags with the same name if they are used by different teams or platforms. +- We have a lot of flags, and we want to evaluate only a subset of them for a specific context _(e.g. on my ios application)_. **For all those points, as of today the only way to achieve this is to run multiple instances of GO Feature Flag, which is not ideal.** From db7a4c7df8e1bd9542828bd3d5233ec1547cb4e7 Mon Sep 17 00:00:00 2001 From: Thomas Poignant Date: Wed, 18 Dec 2024 15:55:50 +0100 Subject: [PATCH 4/8] Add solution 3 Signed-off-by: Thomas Poignant --- testdata/flag-config.yaml | 18 ++++ .../pages/specification/20241027-flagsets.md | 84 ++++++++++++++++++- 2 files changed, 98 insertions(+), 4 deletions(-) diff --git a/testdata/flag-config.yaml b/testdata/flag-config.yaml index c7772da0977..87f9aeafff3 100644 --- a/testdata/flag-config.yaml +++ b/testdata/flag-config.yaml @@ -30,3 +30,21 @@ test-flag2: defaultRule: name: defaultRule variation: Default + +flagSets: + - name: set-team-1 + flags: + test-flag2: + variations: + Default: false + False: false + True: true + targeting: + - name: rule1 + query: key eq "not-a-key" + percentage: + False: 0 + True: 100 + defaultRule: + name: defaultRule + variation: Default \ No newline at end of file diff --git a/website/src/pages/specification/20241027-flagsets.md b/website/src/pages/specification/20241027-flagsets.md index 684be012b27..bc8e837091e 100644 --- a/website/src/pages/specification/20241027-flagsets.md +++ b/website/src/pages/specification/20241027-flagsets.md @@ -8,7 +8,7 @@ Description: Flag sets are a way to group flags together. | | | |----------------------|---------------------------------------------------| | **Creation Date** | 27/10/2024 | -| **Last Update Date** | 27/10/2024 | +| **Last Update Date** | 18/12/2024 | | **Authors** | Thomas Poignant | | **Status** | ![draft](https://img.shields.io/badge/-draft-red) | @@ -173,9 +173,85 @@ authorizedKeys: - If we want the same flag to be part of multiple flag sets, we need to duplicate the flag in multiple files. ### Solution 3 -:::note -Feel free to propose other solutions here. -::: +In this solution we want to offer multiple way to specify the flag set in the configuration. +This solution will be more complex to implement, but it will give more flexibility to the users. + +In this solution the flag set could be defined: +1. In the retriever configuration _(like in solution 2)_. +2. And in the flag configuration file with a specific format. + +#### example: In the retriever configuration + +The relay proxy configuration will look like this: +```yaml +# ... +retrievers: + - kind: file + path: config-file.goff.yaml + flagSet: flagset-teamA +# ... +``` + +And the flag configuration file (`config-file.goff.yaml`) will look like this: +```yaml +# all the flags in this file will be part of the flag set flagset-teamA +# ... +test-flag: + variations: + enabled: true + disabled: false + defaultRule: + variation: enabled +# ... +``` + +#### example: In the flag configuration file with a specific format + +In that case we don't need to specify the flag set in the retriever configuration. +The relay proxy configuration will look like this: +```yaml +# ... +retrievers: + - kind: file + path: config-file.goff.yaml +# ... +``` + +And the flag configuration file (`config-file.goff.yaml`) will look like this: +```yaml +# flagSets is a new key in the configuration file, that allow to create a super set with multiple flag sets. +flagSets: + - name: flagset-teamA + flags: + test-flag2: + variations: + Default: false + False: false + True: true + targeting: + - name: rule1 + query: key eq "not-a-key" + percentage: + False: 0 + True: 100 + defaultRule: + name: defaultRule + variation: Default + - name: flagset-teamB + flags: + test-flag2: + # ... +``` + +**PRO** +- It gives flexibility in the way of creating the flag sets _(in the retriever or in the flag configuration file)_. +- We can create multiple flag sets from a single retriver, by generating multiple flag sets in the flag configuration file. +- We can still have multiple files for 1 flag set _(by specifying the same `flagSet` name in each file)_. +- If we don't want to change the format of our configuration file, we can still set the flag sey in the retriever configuration. + +**CON** +- If we configure a flag set name both in the retriever and in the flag configuration file, we need to decide which one to use _(most likely the one in the file)._ +- It is a bit more complex to implement than the other solutions. ## Decision From d06ea52f2ee203227a9bb1498fa2d0e2fe1eda75 Mon Sep 17 00:00:00 2001 From: Thomas Poignant Date: Wed, 18 Dec 2024 17:11:23 +0100 Subject: [PATCH 5/8] add precision static vs dynamic Signed-off-by: Thomas Poignant --- website/src/pages/specification/20241027-flagsets.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/website/src/pages/specification/20241027-flagsets.md b/website/src/pages/specification/20241027-flagsets.md index bc8e837091e..4a905dd9a10 100644 --- a/website/src/pages/specification/20241027-flagsets.md +++ b/website/src/pages/specification/20241027-flagsets.md @@ -49,7 +49,6 @@ In both cases having a flag set could be beneficial, for **performance reasons** - In the providers, we should be able to specify the flag set to evaluate directly in the constructor _(by providing an API Key, or the name of the flag set)_. - Admin API Keys, should be able to evaluate all the flag sets _(to be able to see all the flags available in the project)_. If none specified in the request, the default flag set should be evaluated. - ## Out of scope for now - Dynamic flag sets based on the evaluation context _([as mentioned in this slack message](https://gophers.slack.com/archives/C029TH8KDFG/p1732703075509229))_. - Single retrievers for multiple flag sets _(as proposed in https://github.com/thomaspoignant/go-feature-flag/issues/2314)_. @@ -180,6 +179,10 @@ In this solution the flag set could be defined: 1. In the retriever configuration _(like in solution 2)_. 2. And in the flag configuration file with a specific format. +This 2 ways of defining a flag set can lead to 2 concepts: +- **static flag set**: the flag set is defined in the retriever configuration, we know in advance that this flag set exists. +- **dynamic flag set**: the flag set is defined in the flag configuration file, we don't know in advance which flag sets exist. + #### example: In the retriever configuration The relay proxy configuration will look like this: @@ -252,6 +255,7 @@ flagSets: **CON** - If we configure a flag set name both in the retriever and in the flag configuration file, we need to decide which one to use _(most likely the one in the file)._ - It is a bit more complex to implement than the other solutions. +- It will not be possible to attach a specific `notifier` or `exporter` to a dynamic flag set. ## Decision From eb49de6919f913d486c16042e257e0a05e7e6e32 Mon Sep 17 00:00:00 2001 From: Thomas Poignant Date: Wed, 18 Dec 2024 17:17:39 +0100 Subject: [PATCH 6/8] add definition of static vs dynamic Signed-off-by: Thomas Poignant --- website/src/pages/specification/20241027-flagsets.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/website/src/pages/specification/20241027-flagsets.md b/website/src/pages/specification/20241027-flagsets.md index 4a905dd9a10..3a3284e4c10 100644 --- a/website/src/pages/specification/20241027-flagsets.md +++ b/website/src/pages/specification/20241027-flagsets.md @@ -18,6 +18,9 @@ Description: Flag sets are a way to group flags together. > _Source: [Openfeature glossary](https://openfeature.dev/specification/glossary/#flag-set)._ +- **static flag set**: the flag set is defined in the retriever configuration, we know in advance that this flag set exists. +- **dynamic flag set**: the flag set is defined in the flag configuration file, we don't know in advance which flag sets exist. + ## Context As of today, GO Feature Flag is evaluating flag differently for client and server evaluations. @@ -117,6 +120,8 @@ authorizedKeys: - The flag set configuration is at the same place as the flag configuration, so it is possible to edit the flag set name directly inside the file _(risk of overriding another flag set)_. - If we want the same flag to be part of multiple flag sets, we need to duplicate the flag in multiple files. +--- + ### Solution 2: specify the flag set in the retriever configuration In this solution we consider that we need at least one file per flag set. @@ -171,6 +176,8 @@ authorizedKeys: **CON** - If we want the same flag to be part of multiple flag sets, we need to duplicate the flag in multiple files. +--- + ### Solution 3 In this solution we want to offer multiple way to specify the flag set in the configuration. This solution will be more complex to implement, but it will give more flexibility to the users. From 4f859b286a50160f6a8b94eede8cf3ce83f3f033 Mon Sep 17 00:00:00 2001 From: Thomas Poignant Date: Thu, 19 Dec 2024 10:05:40 +0100 Subject: [PATCH 7/8] Update flag-config.yaml --- testdata/flag-config.yaml | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/testdata/flag-config.yaml b/testdata/flag-config.yaml index 87f9aeafff3..c7772da0977 100644 --- a/testdata/flag-config.yaml +++ b/testdata/flag-config.yaml @@ -30,21 +30,3 @@ test-flag2: defaultRule: name: defaultRule variation: Default - -flagSets: - - name: set-team-1 - flags: - test-flag2: - variations: - Default: false - False: false - True: true - targeting: - - name: rule1 - query: key eq "not-a-key" - percentage: - False: 0 - True: 100 - defaultRule: - name: defaultRule - variation: Default \ No newline at end of file From c99b587b09e3a6b66ee2167ce64a266e59ac8769 Mon Sep 17 00:00:00 2001 From: Thomas Poignant Date: Thu, 19 Dec 2024 10:06:07 +0100 Subject: [PATCH 8/8] Update 20241027-flagsets.md --- website/src/pages/specification/20241027-flagsets.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/src/pages/specification/20241027-flagsets.md b/website/src/pages/specification/20241027-flagsets.md index 3a3284e4c10..8cc18ea2051 100644 --- a/website/src/pages/specification/20241027-flagsets.md +++ b/website/src/pages/specification/20241027-flagsets.md @@ -174,7 +174,7 @@ authorizedKeys: - The flag set name is directly associated to the retriever, so it is not possible to edit it directly inside the file. **CON** -- If we want the same flag to be part of multiple flag sets, we need to duplicate the flag in multiple files. + ---