|
| 1 | +--- |
| 2 | +status: proposed |
| 3 | +author: @tangenti |
| 4 | +created: 2025-06-27 |
| 5 | +updated: 2025-06-27 |
| 6 | +--- |
| 7 | + |
| 8 | +# Support for Duplicate Flag Keys |
| 9 | + |
| 10 | +This ADR proposes allowing a single sync source to provide multiple flags that share the same key. This enables greater flexibility for modularizing flag configurations. |
| 11 | + |
| 12 | +## Background |
| 13 | + |
| 14 | +Currently, the `flagd` [flag configuration](https://flagd.dev/schema/v0/flags.json) stores flags in a JSON object (a map), where each key must be unique. While the JSON specification technically allows duplicate keys, it's not recommended and not well-supported in the implementations. |
| 15 | + |
| 16 | +This limitation prevents use cases for flag modularization and multi-tenancy, such as: |
| 17 | + |
| 18 | +- **Component-based Flags:** Two different services, each with its own in-process provider, cannot independently define a flag with the same key when communicating with the same `flagd` daemon. |
| 19 | +- **Multi-Tenant Targeting:** A single flagd daemon cannot use the same flag key with different targeting rules for different tenants |
| 20 | + |
| 21 | +## Requirements |
| 22 | + |
| 23 | +- Allow a single sync source to define multiple flags that have the same key. |
| 24 | +- Flags from a sync source with the same keys can have different types and targeting rules. |
| 25 | +- No breaking changes for the current flagd flag configuration schema or flagd sync services. |
| 26 | + |
| 27 | +## Proposal |
| 28 | + |
| 29 | +We will update the `flagd` flag configuration schema to support receiving flags as an **array of flag objects**. The existing schema will remain fully supported. |
| 30 | + |
| 31 | +### API Change |
| 32 | + |
| 33 | +#### Flag Configuration Schema |
| 34 | + |
| 35 | +We'll add a new schema as a [subschema](https://json-schema.org/learn/glossary#subschema) to the existing flagd flag configuration schema. It will be a composite of the original schema except `flags` (`#/definitions/base`), with a new schema for `flags` that allows flags array in addition to the currently supported flags object. The existing main schema will be the composite of `#/definitions/base` and the subschema for the flags object. |
| 36 | + |
| 37 | +```json |
| 38 | +... |
| 39 | +"flagsArray": { |
| 40 | + "type": "array", |
| 41 | + "items": { |
| 42 | + "allOf": [ |
| 43 | + { |
| 44 | + "$ref": "#/definitions/flag" |
| 45 | + }, |
| 46 | + { |
| 47 | + "type": "object", |
| 48 | + "properties": { |
| 49 | + "key": { |
| 50 | + "description": "Key of the flag", |
| 51 | + "type": "string", |
| 52 | + "minLength": 1 |
| 53 | + } |
| 54 | + }, |
| 55 | + "required": [ |
| 56 | + "key" |
| 57 | + ] |
| 58 | + } |
| 59 | + ] |
| 60 | + } |
| 61 | +}, |
| 62 | +"flagsArraySchema": { |
| 63 | + "$id": "https://flagd.dev/schema/v0/flags.json#flagsarray", |
| 64 | + "type": "object", |
| 65 | + "allOf": [ |
| 66 | + { |
| 67 | + "$ref": "#/definitions/base" |
| 68 | + }, |
| 69 | + { |
| 70 | + "properties": { |
| 71 | + "flags": { |
| 72 | + "oneOf": [ |
| 73 | + { |
| 74 | + "$ref": "#/definitions/flagsArray" |
| 75 | + }, |
| 76 | + { |
| 77 | + "$ref": "#/definitions/flagsMap" |
| 78 | + } |
| 79 | + ] |
| 80 | + } |
| 81 | + }, |
| 82 | + "required": [ |
| 83 | + "flags" |
| 84 | + ] |
| 85 | + } |
| 86 | + ] |
| 87 | +} |
| 88 | +... |
| 89 | +``` |
| 90 | + |
| 91 | +If the config level flag set ID is not specified, `metadata.flagSetId` of each flag will be interpreted as its flag set ID. |
| 92 | + |
| 93 | +A flag will be uniquely identified by the composite key `(flagKey, flagSetId)`. The following three flags will be considered as three different flags. |
| 94 | + |
| 95 | +1. `{"flagKey": "enable-feature", "flagSetId": ""}` |
| 96 | +2. `{"flagKey": "enable-feature", "flagSetId": "default"}` |
| 97 | +3. `{"flagKey": "enable-feature", "flagSetId": "beta"}` |
| 98 | + |
| 99 | +### Flagd daemon |
| 100 | + |
| 101 | +Flagd daemon will perform the JSON schema checks with the reference to `https://flagd.dev/schema/v0/flags.json#flagsarray`, allowing both flags as an object and as an array. |
| 102 | + |
| 103 | +If the flag array contains two or more flags with the same composite key, the config will be considered as invalid. |
| 104 | + |
| 105 | +If the request from in-process flagd providers result in a config that has duplicate flag keys, the flagd daemon will only keep one of them in the response. |
| 106 | + |
| 107 | +### Flagd Daemon Storage |
| 108 | + |
| 109 | +1. Flagd will have separate stores for `flags` and `sources`. |
| 110 | + |
| 111 | +1. The `flags` store will use the composite key for flags. |
| 112 | + |
| 113 | +1. `selector` will be removed from the store |
| 114 | + |
| 115 | +1. `flagSetId` will be moved from `source` metadata to `flag` metadata. |
| 116 | + |
| 117 | +### Flags Lifecycle |
| 118 | + |
| 119 | +Currently, the flags configurations from the latest update of a source will trigger a `sync.ALL` sync. If a flag was presented in the previous configuration but not in the current configuration, it will be removed. In another word, the latest source that provides the config for a flag will take the ownership of a flag, and any subsequent configs are considered as the full states of the flags that are owned by the source. |
| 120 | + |
| 121 | +We'll keep the same behaviors with this proposal: |
| 122 | + |
| 123 | +1. If two sources provide the flags with the same composite key, the latest one will be stored. |
| 124 | + |
| 125 | +2. If a flag from a source no longer presents in the latest configuration of the same source, it will be removed. |
| 126 | + |
| 127 | +This behavior is less ideal as the ownership management depends on the ordre of the sync. This should be addressed in a separate ADR. |
| 128 | + |
| 129 | +### Consequences |
| 130 | + |
| 131 | +#### The good |
| 132 | + |
| 133 | +- One source can provide flags with the same keys. |
| 134 | +- Flag set ID no longer bound to a source, so one source can have multiple flag sets. |
| 135 | +- No breaking change of the API definition and the API behaviors. |
| 136 | +- No significant change on the flagd stores and how selections work. |
| 137 | + |
| 138 | +#### The bad |
| 139 | + |
| 140 | +- The proposal still leverages the concept of flag set in the flagd storage. |
| 141 | + |
| 142 | +- The schema does not guarantee that flags of the same flag set from the same source will not have the same keys. This is guaranteed in the proposal of #1634. |
| 143 | + |
| 144 | +- Compared to #1634, this proposal does not allow to define flag set wide metadata. |
0 commit comments