-
-
Notifications
You must be signed in to change notification settings - Fork 151
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
docs(specification): Flag set specification proposal #2734
base: main
Are you sure you want to change the base?
Changes from 1 commit
068a9a5
185aee7
8857eda
db7a4c7
3b7b4cb
d06ea52
eb49de6
4f859b2
c99b587
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think by doing it this way it ends up more flexible. Since it becomes part of the retriever config and not the flag definition itself it won't be necessary to come up with different approaches to retrievers like Redis and MongoDB. Does it make sense? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes you are right, it makes more sense to put it outside of the configuration and move it to the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm a little confused how this would work in practice though? If tomorrow a new team comes in and they need their own flag set, then GOFF would need to be redeployed again with a new configuration for another retriever. In our companies' deployment/FF solution, we're hoping each development project in each team is an individual flag set to avoid possible conflict, which means every new project on every team that's created would require a production redeployment of go-feature-flag on our side. Obviously this isn't really maintainable, which is what our feature request aimed to solve. Disregarding the feature request previously, would another solution such as having the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
@iisharankov your use case is still in my mind but I want to first introduce the notion of flag set and later address your usecase. I place your need for now in the Out of scope section, but as soon as we have the flag set ready we will be able to work on this too.
Yes that was my original idea, but we will have limits because of unicity of flag name here. |
||
- kind: file | ||
path: config-file2.goff.yaml | ||
flagSet: flagset-teamB | ||
|
||
authorizedKeys: | ||
evaluation: | ||
- apikey1 # owner: userID1 | ||
- apikey2 # owner: userID2 | ||
admin: | ||
- apikey3 | ||
Comment on lines
+162
to
+167
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🤔 I am not sure how to configure the relation between the API keys and the flag sets, all proposals are welcome on this. |
||
``` | ||
### Solution 3 | ||
:::note | ||
Feel free to propose other solutions here. | ||
::: | ||
|
||
## Decision | ||
|
||
## Consequences |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🤔 I am not sure how to configure the relation between the API keys and the flag sets, all proposals are welcome on this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I just went through the API keys configuration docs and it looks like it accept any string. Since it's an array it can be organized so that the available flag sets for a specific api key are declared as well in the config file.
Another approach would be to generate something like JWTs to be used as API keys and encode flag set access data as claims. This path would be much more complex
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes it can be anything here.
I am just not sure how to represent the configuration without breaking changes for existing config 🤔.
I was also thinking about that, but it could be an evolution for a later stage.