@bluealba/platform-cli 1.0.1 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +278 -15
- package/docs/404.mdx +5 -0
- package/docs/architecture/api-explorer.mdx +478 -0
- package/docs/architecture/architecture-diagrams.mdx +12 -0
- package/docs/architecture/authentication-system.mdx +903 -0
- package/docs/architecture/authorization-system.mdx +886 -0
- package/docs/architecture/bootstrap.mdx +1442 -0
- package/docs/architecture/gateway-architecture.mdx +845 -0
- package/docs/architecture/multi-tenancy.mdx +1150 -0
- package/docs/architecture/overview.mdx +776 -0
- package/docs/architecture/scheduler.mdx +818 -0
- package/docs/architecture/shell.mdx +885 -0
- package/docs/architecture/ui-extension-points.mdx +781 -0
- package/docs/architecture/user-states.mdx +794 -0
- package/docs/development/overview.mdx +21 -0
- package/docs/development/workflow.mdx +914 -0
- package/docs/getting-started/core-concepts.mdx +892 -0
- package/docs/getting-started/installation.mdx +780 -0
- package/docs/getting-started/overview.mdx +83 -0
- package/docs/getting-started/quick-start.mdx +940 -0
- package/docs/guides/adding-documentation-sites.mdx +1367 -0
- package/docs/guides/creating-services.mdx +1736 -0
- package/docs/guides/creating-ui-modules.mdx +1860 -0
- package/docs/guides/identity-providers.mdx +1007 -0
- package/docs/guides/mermaid-diagrams.mdx +212 -0
- package/docs/guides/using-feature-flags.mdx +1059 -0
- package/docs/guides/working-with-rooms.mdx +566 -0
- package/docs/index.mdx +57 -0
- package/docs/platform-cli/commands.mdx +604 -0
- package/docs/platform-cli/overview.mdx +195 -0
- package/package.json +5 -2
- package/skills/ba-platform/platform-cli.skill.md +26 -0
- package/skills/ba-platform/platform.skill.md +35 -0
- package/templates/application-monorepo-template/gitignore +95 -0
- package/templates/bootstrap-service-template/Dockerfile.development +1 -1
- package/templates/bootstrap-service-template/gitignore +57 -0
- package/templates/bootstrap-service-template/package.json +1 -1
- package/templates/bootstrap-service-template/src/main.ts +6 -16
- package/templates/customization-ui-module-template/Dockerfile.development +1 -1
- package/templates/customization-ui-module-template/gitignore +73 -0
- package/templates/nestjs-service-module-template/Dockerfile.development +1 -1
- package/templates/nestjs-service-module-template/gitignore +56 -0
- package/templates/platform-init-template/{{platformName}}-core/gitignore +97 -0
- package/templates/platform-init-template/{{platformName}}-core/local/.env.example +1 -1
- package/templates/platform-init-template/{{platformName}}-core/local/platform-docker-compose.yml +1 -1
- package/templates/platform-init-template/{{platformName}}-core/local/{{platformName}}-core-docker-compose.yml +0 -1
- package/templates/react-ui-module-template/Dockerfile +1 -1
- package/templates/react-ui-module-template/Dockerfile.development +1 -3
- package/templates/react-ui-module-template/caddy/Caddyfile +1 -1
- package/templates/react-ui-module-template/gitignore +72 -0
- package/templates/react-ui-module-template/Dockerfile_nginx +0 -11
- package/templates/react-ui-module-template/nginx/default.conf +0 -23
|
@@ -0,0 +1,1059 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Using Feature Flags
|
|
3
|
+
description: Complete guide to implementing feature flags in the Blue Alba Platform for controlled feature rollouts, A/B testing, and dynamic configuration
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
import { Card, CardGrid, Aside, Tabs, TabItem } from '@astrojs/starlight/components';
|
|
7
|
+
import MermaidDiagram from '~/components/MermaidDiagram.astro';
|
|
8
|
+
|
|
9
|
+
Feature flags (also called feature toggles) are a powerful technique that allows you to control feature availability dynamically without deploying new code. The Blue Alba Platform provides a comprehensive feature flags system backed by PostgreSQL with composable conditions and typed values.
|
|
10
|
+
|
|
11
|
+
## Overview
|
|
12
|
+
|
|
13
|
+
The platform's feature flags system enables teams to:
|
|
14
|
+
|
|
15
|
+
- **Toggle features dynamically** - Enable/disable features without redeployment
|
|
16
|
+
- **Target specific users/tenants** - Release features to specific audiences
|
|
17
|
+
- **Return typed values** - Flags can return any value type, not just booleans
|
|
18
|
+
- **Reduce deployment risk** - Deploy code with features disabled, enable when ready
|
|
19
|
+
- **Organize by application** - Associate flags with the application that owns them
|
|
20
|
+
|
|
21
|
+
### Key Benefits
|
|
22
|
+
|
|
23
|
+
<CardGrid stagger>
|
|
24
|
+
<Card title="Progressive Rollouts" icon="rocket">
|
|
25
|
+
Release features gradually to control risk. Use percentage-based rollout with consistent hashing to ensure users always see the same variant.
|
|
26
|
+
</Card>
|
|
27
|
+
|
|
28
|
+
<Card title="Instant Rollback" icon="setting">
|
|
29
|
+
Disable problematic features instantly without rolling back deployments or redeploying code.
|
|
30
|
+
</Card>
|
|
31
|
+
|
|
32
|
+
<Card title="Tenant Isolation" icon="lock">
|
|
33
|
+
Enable features for specific tenants while keeping them disabled for others.
|
|
34
|
+
</Card>
|
|
35
|
+
|
|
36
|
+
<Card title="Application Ownership" icon="document">
|
|
37
|
+
Associate each flag with the application that owns it, making it easier to manage and filter flags across a large platform.
|
|
38
|
+
</Card>
|
|
39
|
+
</CardGrid>
|
|
40
|
+
|
|
41
|
+
---
|
|
42
|
+
|
|
43
|
+
## Architecture
|
|
44
|
+
|
|
45
|
+
The feature flags system is a direct database-backed implementation with no provider abstraction layer. The service composes three focused collaborators — `ConditionEvaluatorService`, `RolloutService`, and `FeatureFlagsDatabaseService` — all wired together in the gateway.
|
|
46
|
+
|
|
47
|
+
<MermaidDiagram
|
|
48
|
+
title="Feature Flags Architecture"
|
|
49
|
+
code={`graph TB
|
|
50
|
+
subgraph Client["Client Applications"]
|
|
51
|
+
UI[React UI<br/>useFeatureFlag]
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
subgraph Gateway["Gateway Service"]
|
|
55
|
+
API[Feature Flags<br/>Controller]
|
|
56
|
+
Core[Feature Flags<br/>Service]
|
|
57
|
+
Cond[Condition<br/>Evaluator]
|
|
58
|
+
Rollout[Rollout<br/>Service]
|
|
59
|
+
DB2[Database<br/>Service]
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
subgraph Backend["Backend Services"]
|
|
63
|
+
SVC[NestJS Service<br/>FeatureFlagsClientService<br/>@FeatureFlags decorator]
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
subgraph Storage["Storage"]
|
|
67
|
+
DB[(PostgreSQL<br/>Database)]
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
UI -->|HTTP| API
|
|
71
|
+
API --> Core
|
|
72
|
+
Core --> Cond
|
|
73
|
+
Core --> Rollout
|
|
74
|
+
Core --> DB2
|
|
75
|
+
DB2 -->|Direct| DB
|
|
76
|
+
Gateway -->|x-forwarded-feature-flags| SVC
|
|
77
|
+
|
|
78
|
+
classDef client fill:#87CEEB,color:#333
|
|
79
|
+
classDef gateway fill:#90EE90,color:#333
|
|
80
|
+
classDef storage fill:#FFD700,color:#333
|
|
81
|
+
classDef backend fill:#DDA0DD,color:#333
|
|
82
|
+
|
|
83
|
+
class UI client
|
|
84
|
+
class API,Core,Cond,Rollout,DB2 gateway
|
|
85
|
+
class SVC backend
|
|
86
|
+
class DB storage`}
|
|
87
|
+
/>
|
|
88
|
+
|
|
89
|
+
### Architecture Components
|
|
90
|
+
|
|
91
|
+
- **Client SDKs**: React hooks library (`useFeatureFlag`, `FeatureFlagGuard`) for consuming feature flags in UI applications
|
|
92
|
+
- **Backend SDKs**: NestJS utilities (`FeatureFlagsClientService`, `@FeatureFlags` decorator) from `@bluealba/pae-service-nestjs-sdk` for consuming feature flags in backend services via the `x-forwarded-feature-flags` header
|
|
93
|
+
- **Gateway API**: REST endpoints for evaluating and managing feature flags
|
|
94
|
+
- **Feature Flags Service**: Orchestrates evaluation logic by composing the three collaborators below
|
|
95
|
+
- **Condition Evaluator**: Evaluates leaf and composite conditions against the request context
|
|
96
|
+
- **Rollout Service**: Applies consistent hash-based percentage rollout gating
|
|
97
|
+
- **Database Service**: Direct PostgreSQL access for reading and writing flag configurations
|
|
98
|
+
|
|
99
|
+
This architecture provides:
|
|
100
|
+
- **Consistency**: Same API across frontend and backend
|
|
101
|
+
- **Performance**: Bulk evaluation support
|
|
102
|
+
- **Self-Contained**: Requires no external services
|
|
103
|
+
|
|
104
|
+
---
|
|
105
|
+
|
|
106
|
+
## Core Concepts
|
|
107
|
+
|
|
108
|
+
### Feature Flags
|
|
109
|
+
|
|
110
|
+
A feature flag is a configuration entry that controls the value returned for a feature in a given context. Each flag has:
|
|
111
|
+
|
|
112
|
+
- **name**: Unique identifier for the flag (e.g., `new-dashboard`)
|
|
113
|
+
- **description**: Human-readable explanation of what the flag controls
|
|
114
|
+
- **visible**: Boolean master switch; when `false` the flag is inactive and evaluation returns `null` (not `defaultValue`). The simulate endpoint returns `defaultValue` with reason `FLAG_INACTIVE` for easier debugging.
|
|
115
|
+
- **defaultValue**: The value returned when no `valueRule` matches the current context
|
|
116
|
+
- **valueRules**: Ordered array of rules evaluated in sequence; the first matching rule's value is returned
|
|
117
|
+
- **application**: The platform application this flag belongs to (optional but strongly recommended)
|
|
118
|
+
|
|
119
|
+
### Application Association
|
|
120
|
+
|
|
121
|
+
Each feature flag can be associated with a specific platform application. This association:
|
|
122
|
+
|
|
123
|
+
- **Identifies ownership** - Makes it clear which application or team is responsible for a flag
|
|
124
|
+
- **Enables filtering** - The Admin UI allows filtering the flags list by application, which is essential when the platform hosts many flags across multiple applications
|
|
125
|
+
- **Supports bootstrap** - When declaring flags in bootstrap configuration, you can reference an application by name and the platform resolves the association automatically
|
|
126
|
+
|
|
127
|
+
<Aside type="tip">
|
|
128
|
+
Although the `application` field is optional at the API level, assigning every feature flag to an application is strongly recommended. As the number of flags grows across the platform, the application association becomes the primary way to organize and locate flags in the Admin UI.
|
|
129
|
+
</Aside>
|
|
130
|
+
|
|
131
|
+
An application is identified by its **internal name** (e.g., `pae-features`) when used in bootstrap or API calls, while the Admin UI presents its **display name** (e.g., `Features`) for readability.
|
|
132
|
+
|
|
133
|
+
```typescript
|
|
134
|
+
interface Application {
|
|
135
|
+
id: number;
|
|
136
|
+
name: string; // Internal application name, used in API/bootstrap
|
|
137
|
+
displayName: string; // Human-readable name, shown in Admin UI
|
|
138
|
+
}
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
### Feature Flag Context
|
|
142
|
+
|
|
143
|
+
When evaluating feature flags, the system uses a flat context interface to make targeting decisions. The context contains information about the current user, tenant, and any additional properties your application needs.
|
|
144
|
+
|
|
145
|
+
```typescript
|
|
146
|
+
interface FeatureFlagContext {
|
|
147
|
+
username?: string; // Current username
|
|
148
|
+
tenant?: string; // Current tenant identifier
|
|
149
|
+
[key: string]: string | number | boolean | undefined; // Additional custom attributes
|
|
150
|
+
}
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
<Aside type="note">
|
|
154
|
+
The context interface is intentionally flat. Unlike the legacy model there is no nested `properties` bag — custom attributes are placed directly on the context object. The special attribute name `user` in a condition maps to `context.username`.
|
|
155
|
+
</Aside>
|
|
156
|
+
|
|
157
|
+
### Value Rules
|
|
158
|
+
|
|
159
|
+
Value rules are the targeting rules that determine what value a flag returns for a given context.
|
|
160
|
+
|
|
161
|
+
**Value Rule Structure:**
|
|
162
|
+
|
|
163
|
+
```typescript
|
|
164
|
+
interface ValueRule {
|
|
165
|
+
name?: string; // Optional human-readable identifier (improves log messages)
|
|
166
|
+
condition: LeafCondition | CompositeCondition; // Targeting condition
|
|
167
|
+
value: any; // Value to return when condition matches
|
|
168
|
+
rollout?: RolloutConfig; // Optional percentage-based rollout
|
|
169
|
+
}
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
**How Value Rules Work:**
|
|
173
|
+
|
|
174
|
+
- Rules are evaluated **in insertion order (by database `id`)** against the current context
|
|
175
|
+
- The **first rule whose condition matches** wins; its `value` is returned immediately
|
|
176
|
+
- If **no rule matches**, the flag's `defaultValue` is returned
|
|
177
|
+
- If the flag's `visible` field is `false`, evaluation is skipped entirely and `null` is returned (not `defaultValue`)
|
|
178
|
+
|
|
179
|
+
### Conditions
|
|
180
|
+
|
|
181
|
+
Conditions are the building blocks of value rule targeting. There are two kinds: leaf conditions and composite conditions. They can be nested arbitrarily deep.
|
|
182
|
+
|
|
183
|
+
#### Leaf Conditions
|
|
184
|
+
|
|
185
|
+
A leaf condition compares a single context attribute against a value or set of values.
|
|
186
|
+
|
|
187
|
+
```typescript
|
|
188
|
+
interface LeafCondition {
|
|
189
|
+
attribute: string; // Context attribute to inspect (e.g., 'tenant', 'role')
|
|
190
|
+
operator: 'eq' | 'neq' | 'in' | 'nin'; // Comparison operator
|
|
191
|
+
value: string | number | boolean | Array<string | number | boolean>;
|
|
192
|
+
}
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
**Operators:**
|
|
196
|
+
|
|
197
|
+
| Operator | Description | Example Use Case |
|
|
198
|
+
|----------|-------------|------------------|
|
|
199
|
+
| `eq` | Attribute equals value | Target a single tenant |
|
|
200
|
+
| `neq` | Attribute does not equal value | Exclude a specific user |
|
|
201
|
+
| `in` | Attribute is in the list | Target a set of tenants or roles |
|
|
202
|
+
| `nin` | Attribute is NOT in the list | Exclude a set of users |
|
|
203
|
+
|
|
204
|
+
<Aside type="note">
|
|
205
|
+
The attribute name `user` is a reserved alias that maps to `context.username`. For all other attributes, the name must match a key on the `FeatureFlagContext` object.
|
|
206
|
+
</Aside>
|
|
207
|
+
|
|
208
|
+
**Example Leaf Condition:**
|
|
209
|
+
|
|
210
|
+
```typescript
|
|
211
|
+
{
|
|
212
|
+
attribute: 'tenant',
|
|
213
|
+
operator: 'in',
|
|
214
|
+
value: ['premium-tenant-1', 'premium-tenant-2']
|
|
215
|
+
}
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
#### Composite Conditions
|
|
219
|
+
|
|
220
|
+
Composite conditions combine multiple conditions using a logical operator. They are recursive — each entry in `rules` can itself be a leaf or another composite condition.
|
|
221
|
+
|
|
222
|
+
```typescript
|
|
223
|
+
interface CompositeCondition {
|
|
224
|
+
operator: 'AND' | 'OR';
|
|
225
|
+
rules: Array<LeafCondition | CompositeCondition>;
|
|
226
|
+
}
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
**Example Composite Condition:**
|
|
230
|
+
|
|
231
|
+
```typescript
|
|
232
|
+
{
|
|
233
|
+
operator: 'AND',
|
|
234
|
+
rules: [
|
|
235
|
+
{
|
|
236
|
+
attribute: 'tenant',
|
|
237
|
+
operator: 'in',
|
|
238
|
+
value: ['premium-tenant-1', 'premium-tenant-2']
|
|
239
|
+
},
|
|
240
|
+
{
|
|
241
|
+
attribute: 'user',
|
|
242
|
+
operator: 'neq',
|
|
243
|
+
value: 'test-user@example.com'
|
|
244
|
+
}
|
|
245
|
+
]
|
|
246
|
+
}
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
This matches users in a premium tenant, excluding the test user.
|
|
250
|
+
|
|
251
|
+
### Percentage-Based Rollout
|
|
252
|
+
|
|
253
|
+
Value rules support an optional `rollout` configuration that restricts the rule to a percentage of the target population.
|
|
254
|
+
|
|
255
|
+
```typescript
|
|
256
|
+
interface RolloutConfig {
|
|
257
|
+
percentage: number; // 0–100 (inclusive)
|
|
258
|
+
attribute: string; // Context attribute used as the hashing key
|
|
259
|
+
}
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
When `rollout` is present, the rule is only applied if the SHA-256 hash of `flagName:attributeValue` falls within the given percentage bucket. This ensures:
|
|
263
|
+
|
|
264
|
+
- **Consistency**: The same user always falls into the same bucket for the same flag
|
|
265
|
+
- **Stability**: Adding or removing other rules does not change which bucket a user belongs to
|
|
266
|
+
|
|
267
|
+
**Example — Roll out to 20% of users:**
|
|
268
|
+
|
|
269
|
+
```typescript
|
|
270
|
+
{
|
|
271
|
+
condition: {
|
|
272
|
+
attribute: 'tenant',
|
|
273
|
+
operator: 'eq',
|
|
274
|
+
value: 'acme-corp'
|
|
275
|
+
},
|
|
276
|
+
value: true,
|
|
277
|
+
rollout: {
|
|
278
|
+
percentage: 20,
|
|
279
|
+
attribute: 'username'
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
---
|
|
285
|
+
|
|
286
|
+
## Implementation Details
|
|
287
|
+
|
|
288
|
+
The feature flags implementation is built directly into the gateway service. All flag configurations are stored in the gateway's PostgreSQL database and the service directly composes `FeatureFlagsDatabaseService`, `ConditionEvaluatorService`, and `RolloutService` to handle evaluation.
|
|
289
|
+
|
|
290
|
+
### Capabilities
|
|
291
|
+
|
|
292
|
+
- **Database-Backed Storage**: All flags stored in PostgreSQL
|
|
293
|
+
- **Zero External Dependencies**: No external services required
|
|
294
|
+
- **Typed Values**: Flags can return any value type, not just booleans
|
|
295
|
+
- **Composable Conditions**: Leaf and composite conditions with arbitrary nesting
|
|
296
|
+
- **Ordered Value Rules**: First-match-wins evaluation order (by insertion order)
|
|
297
|
+
- **Percentage Rollouts**: Consistent hash-based rollout with configurable attribute
|
|
298
|
+
- **Application Association**: Flags can be linked to a specific platform application
|
|
299
|
+
|
|
300
|
+
### Example Feature Flag Structure
|
|
301
|
+
|
|
302
|
+
Here is a complete example of a feature flag with value rules and an application association:
|
|
303
|
+
|
|
304
|
+
```json
|
|
305
|
+
{
|
|
306
|
+
"name": "advanced-analytics-dashboard",
|
|
307
|
+
"description": "Enable advanced analytics dashboard for specific tenants",
|
|
308
|
+
"visible": true,
|
|
309
|
+
"defaultValue": false,
|
|
310
|
+
"application": {
|
|
311
|
+
"id": 3,
|
|
312
|
+
"name": "pae-analytics",
|
|
313
|
+
"displayName": "Analytics"
|
|
314
|
+
},
|
|
315
|
+
"valueRules": [
|
|
316
|
+
{
|
|
317
|
+
"name": "premium-tenants-no-test",
|
|
318
|
+
"condition": {
|
|
319
|
+
"operator": "AND",
|
|
320
|
+
"rules": [
|
|
321
|
+
{
|
|
322
|
+
"attribute": "tenant",
|
|
323
|
+
"operator": "in",
|
|
324
|
+
"value": ["premium-tenant-1", "premium-tenant-2"]
|
|
325
|
+
},
|
|
326
|
+
{
|
|
327
|
+
"attribute": "user",
|
|
328
|
+
"operator": "neq",
|
|
329
|
+
"value": "test-user@example.com"
|
|
330
|
+
}
|
|
331
|
+
]
|
|
332
|
+
},
|
|
333
|
+
"value": true
|
|
334
|
+
}
|
|
335
|
+
]
|
|
336
|
+
}
|
|
337
|
+
```
|
|
338
|
+
|
|
339
|
+
**Result**: This flag returns `true` for users in `premium-tenant-1` or `premium-tenant-2`, excluding `test-user@example.com`. All other evaluations return the `defaultValue` of `false`. It is owned by the `pae-analytics` application.
|
|
340
|
+
|
|
341
|
+
---
|
|
342
|
+
|
|
343
|
+
## Managing Feature Flags in the Admin UI
|
|
344
|
+
|
|
345
|
+
The Platform Admin application (`pae-admin-ui`) provides a dedicated Feature Flags page where you can create, edit, and delete flags, as well as filter them by application.
|
|
346
|
+
|
|
347
|
+
### Filtering by Application
|
|
348
|
+
|
|
349
|
+
The Feature Flags page includes an **Application selector** in the top bar. Selecting an application filters the table to show only flags that belong to that application. The default value shows flags for all applications.
|
|
350
|
+
|
|
351
|
+
This is the primary way to work with flags when a platform has many applications — each team selects their application to see only the flags they own.
|
|
352
|
+
|
|
353
|
+
### Creating a Feature Flag
|
|
354
|
+
|
|
355
|
+
To create a new feature flag from the Admin UI:
|
|
356
|
+
|
|
357
|
+
1. Navigate to **Feature Flags** in the Admin application.
|
|
358
|
+
2. Click **Add Feature Flag**.
|
|
359
|
+
3. Fill in the required fields in the dialog:
|
|
360
|
+
|
|
361
|
+
| Field | Required | Description |
|
|
362
|
+
|-------|----------|-------------|
|
|
363
|
+
| **Name** | Yes | Unique identifier. Kebab-case, no spaces (e.g., `new-checkout-flow`). Cannot be changed after creation. |
|
|
364
|
+
| **Application** | Yes | The platform application this flag belongs to. |
|
|
365
|
+
| **Description** | No | Human-readable explanation of what the flag controls. |
|
|
366
|
+
| **Visible** | Yes | Master switch. When off, evaluation always returns the default value. Defaults to `true`. |
|
|
367
|
+
| **Default Value** | No | Value returned when no value rule matches. |
|
|
368
|
+
| **Value Rules** | No | Ordered targeting rules; the first matching rule's value is returned. |
|
|
369
|
+
|
|
370
|
+
4. Click **Create** to save the flag.
|
|
371
|
+
|
|
372
|
+
<Aside type="note">
|
|
373
|
+
The **Application** field is required in the Admin UI form. This ensures every flag created through the UI is properly organized and discoverable.
|
|
374
|
+
</Aside>
|
|
375
|
+
|
|
376
|
+
### Editing a Feature Flag
|
|
377
|
+
|
|
378
|
+
To edit an existing flag:
|
|
379
|
+
|
|
380
|
+
1. Locate the flag in the table (use the Application selector or the search input to narrow down results).
|
|
381
|
+
2. Click the actions menu (three-dot icon) on the flag row.
|
|
382
|
+
3. Select **Edit**.
|
|
383
|
+
4. Update any field except **Name** (the name is immutable after creation).
|
|
384
|
+
5. Click **Save Changes**.
|
|
385
|
+
|
|
386
|
+
<Aside type="caution">
|
|
387
|
+
When you save an edit that includes changes to value rules, **all existing value rules are replaced** by the new set. This is an atomic replacement, not a merge. If you clear all value rules, the flag will always return its `defaultValue`.
|
|
388
|
+
</Aside>
|
|
389
|
+
|
|
390
|
+
### Searching and Filtering
|
|
391
|
+
|
|
392
|
+
The Feature Flags table provides two ways to narrow down results:
|
|
393
|
+
|
|
394
|
+
- **Application selector** (top bar): Filters flags by their associated application. Selecting "All Applications" shows every flag regardless of association.
|
|
395
|
+
- **Search input** (above the table): Filters flags whose name or description match the search term.
|
|
396
|
+
|
|
397
|
+
Both filters are applied simultaneously, so you can select an application and then search within it.
|
|
398
|
+
|
|
399
|
+
---
|
|
400
|
+
|
|
401
|
+
## Declaring Feature Flags via Bootstrap
|
|
402
|
+
|
|
403
|
+
The bootstrap mechanism allows applications to declare their required feature flags as code, ensuring they are created (or updated) automatically when the platform initializes. This is the recommended way to manage flags for an application — it makes the flag definitions version-controlled and reproducible.
|
|
404
|
+
|
|
405
|
+
For the full reference on configuring `feature-flags.json`, including examples and upsert semantics, see the [Bootstrap Architecture documentation](/_/docs/architecture/bootstrap/#feature-flags-definition).
|
|
406
|
+
|
|
407
|
+
### Direct API Usage
|
|
408
|
+
|
|
409
|
+
You can also manage flags directly via the gateway REST API (internal endpoint secured at the network level under `/_/`).
|
|
410
|
+
|
|
411
|
+
<Tabs>
|
|
412
|
+
<TabItem label="Create flag">
|
|
413
|
+
```bash
|
|
414
|
+
POST /_/feature-flags
|
|
415
|
+
Content-Type: application/json
|
|
416
|
+
|
|
417
|
+
{
|
|
418
|
+
"name": "new-checkout-flow",
|
|
419
|
+
"description": "Enable the redesigned checkout experience",
|
|
420
|
+
"visible": true,
|
|
421
|
+
"defaultValue": false,
|
|
422
|
+
"applicationId": 5,
|
|
423
|
+
"valueRules": [
|
|
424
|
+
{
|
|
425
|
+
"name": "premium-tenants",
|
|
426
|
+
"condition": {
|
|
427
|
+
"attribute": "tenant",
|
|
428
|
+
"operator": "in",
|
|
429
|
+
"value": ["premium-tenant-1", "premium-tenant-2"]
|
|
430
|
+
},
|
|
431
|
+
"value": true
|
|
432
|
+
}
|
|
433
|
+
]
|
|
434
|
+
}
|
|
435
|
+
```
|
|
436
|
+
|
|
437
|
+
`valueRules` is optional. If omitted, the flag is created with no rules and every evaluation returns `defaultValue`.
|
|
438
|
+
</TabItem>
|
|
439
|
+
<TabItem label="Update flag with value rules">
|
|
440
|
+
```bash
|
|
441
|
+
PATCH /_/feature-flags/new-checkout-flow
|
|
442
|
+
Content-Type: application/json
|
|
443
|
+
|
|
444
|
+
{
|
|
445
|
+
"visible": true,
|
|
446
|
+
"defaultValue": false,
|
|
447
|
+
"valueRules": [
|
|
448
|
+
{
|
|
449
|
+
"name": "premium-tenants",
|
|
450
|
+
"condition": {
|
|
451
|
+
"attribute": "tenant",
|
|
452
|
+
"operator": "in",
|
|
453
|
+
"value": ["premium-tenant-1", "premium-tenant-2"]
|
|
454
|
+
},
|
|
455
|
+
"value": true
|
|
456
|
+
},
|
|
457
|
+
{
|
|
458
|
+
"name": "admin-internal-50pct",
|
|
459
|
+
"condition": {
|
|
460
|
+
"operator": "AND",
|
|
461
|
+
"rules": [
|
|
462
|
+
{ "attribute": "role", "operator": "eq", "value": "admin" },
|
|
463
|
+
{ "attribute": "tenant", "operator": "eq", "value": "internal" }
|
|
464
|
+
]
|
|
465
|
+
},
|
|
466
|
+
"value": true,
|
|
467
|
+
"rollout": {
|
|
468
|
+
"percentage": 50,
|
|
469
|
+
"attribute": "username"
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
]
|
|
473
|
+
}
|
|
474
|
+
```
|
|
475
|
+
</TabItem>
|
|
476
|
+
<TabItem label="Get all flags">
|
|
477
|
+
```bash
|
|
478
|
+
GET /_/feature-flags/all
|
|
479
|
+
```
|
|
480
|
+
|
|
481
|
+
Each flag in the response includes `visible`, `defaultValue`, `valueRules`, and an optional `application` object:
|
|
482
|
+
|
|
483
|
+
```json
|
|
484
|
+
[
|
|
485
|
+
{
|
|
486
|
+
"id": 12,
|
|
487
|
+
"name": "new-checkout-flow",
|
|
488
|
+
"description": "Enable the redesigned checkout experience",
|
|
489
|
+
"visible": true,
|
|
490
|
+
"defaultValue": false,
|
|
491
|
+
"application": {
|
|
492
|
+
"id": 5,
|
|
493
|
+
"name": "pae-commerce",
|
|
494
|
+
"displayName": "Commerce"
|
|
495
|
+
},
|
|
496
|
+
"valueRules": [],
|
|
497
|
+
"createdAt": "2026-01-10T09:00:00Z",
|
|
498
|
+
"updatedAt": "2026-01-10T09:00:00Z"
|
|
499
|
+
}
|
|
500
|
+
]
|
|
501
|
+
```
|
|
502
|
+
</TabItem>
|
|
503
|
+
<TabItem label="Evaluate a flag">
|
|
504
|
+
```bash
|
|
505
|
+
POST /_/feature-flags/evaluate/new-checkout-flow
|
|
506
|
+
```
|
|
507
|
+
|
|
508
|
+
This endpoint takes **no request body**. The evaluation context (username, tenant, role) is derived from the authenticated user associated with the request. To test arbitrary contexts without being tied to an authenticated user, use the simulate endpoint instead.
|
|
509
|
+
|
|
510
|
+
Response:
|
|
511
|
+
|
|
512
|
+
```json
|
|
513
|
+
{
|
|
514
|
+
"flagName": "new-checkout-flow",
|
|
515
|
+
"value": true,
|
|
516
|
+
"evaluatedAt": "2026-01-10T09:00:00Z"
|
|
517
|
+
}
|
|
518
|
+
```
|
|
519
|
+
</TabItem>
|
|
520
|
+
<TabItem label="Get flag details">
|
|
521
|
+
```bash
|
|
522
|
+
GET /_/feature-flags/new-checkout-flow
|
|
523
|
+
```
|
|
524
|
+
|
|
525
|
+
Returns full details of a single flag including all value rules and application association.
|
|
526
|
+
|
|
527
|
+
Response:
|
|
528
|
+
|
|
529
|
+
```json
|
|
530
|
+
{
|
|
531
|
+
"id": 12,
|
|
532
|
+
"name": "new-checkout-flow",
|
|
533
|
+
"description": "Enable the redesigned checkout experience",
|
|
534
|
+
"visible": true,
|
|
535
|
+
"defaultValue": false,
|
|
536
|
+
"application": {
|
|
537
|
+
"id": 5,
|
|
538
|
+
"name": "pae-commerce",
|
|
539
|
+
"displayName": "Commerce"
|
|
540
|
+
},
|
|
541
|
+
"valueRules": [
|
|
542
|
+
{
|
|
543
|
+
"id": 1,
|
|
544
|
+
"name": "premium-tenants",
|
|
545
|
+
"condition": {
|
|
546
|
+
"attribute": "tenant",
|
|
547
|
+
"operator": "in",
|
|
548
|
+
"value": ["premium-tenant-1", "premium-tenant-2"]
|
|
549
|
+
},
|
|
550
|
+
"value": true,
|
|
551
|
+
"rollout": null,
|
|
552
|
+
"createdAt": "2026-01-10T09:00:00Z",
|
|
553
|
+
"updatedAt": "2026-01-10T09:00:00Z"
|
|
554
|
+
}
|
|
555
|
+
],
|
|
556
|
+
"createdAt": "2026-01-10T09:00:00Z",
|
|
557
|
+
"updatedAt": "2026-01-10T09:00:00Z",
|
|
558
|
+
"createdBy": "admin",
|
|
559
|
+
"updatedBy": "admin"
|
|
560
|
+
}
|
|
561
|
+
```
|
|
562
|
+
</TabItem>
|
|
563
|
+
<TabItem label="Simulate evaluation">
|
|
564
|
+
```bash
|
|
565
|
+
POST /_/feature-flags/simulate/new-checkout-flow
|
|
566
|
+
Content-Type: application/json
|
|
567
|
+
|
|
568
|
+
{
|
|
569
|
+
"context": {
|
|
570
|
+
"username": "alice@example.com",
|
|
571
|
+
"tenant": "premium-tenant-1",
|
|
572
|
+
"role": "user"
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
```
|
|
576
|
+
|
|
577
|
+
The context is supplied directly in the request body and is **not** derived from the authenticated user. This makes the endpoint safe for arbitrary testing during development.
|
|
578
|
+
|
|
579
|
+
Response (rule matched):
|
|
580
|
+
|
|
581
|
+
```json
|
|
582
|
+
{
|
|
583
|
+
"flagName": "new-checkout-flow",
|
|
584
|
+
"active": true,
|
|
585
|
+
"value": true,
|
|
586
|
+
"reason": "RULE_MATCHED",
|
|
587
|
+
"matchedRule": {
|
|
588
|
+
"name": "premium-tenants",
|
|
589
|
+
"condition": {
|
|
590
|
+
"attribute": "tenant",
|
|
591
|
+
"operator": "in",
|
|
592
|
+
"value": ["premium-tenant-1", "premium-tenant-2"]
|
|
593
|
+
},
|
|
594
|
+
"value": true,
|
|
595
|
+
"rollout": null
|
|
596
|
+
},
|
|
597
|
+
"evaluatedAt": "2026-01-10T09:00:00Z",
|
|
598
|
+
"context": {
|
|
599
|
+
"username": "alice@example.com",
|
|
600
|
+
"tenant": "premium-tenant-1",
|
|
601
|
+
"role": "user"
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
```
|
|
605
|
+
</TabItem>
|
|
606
|
+
<TabItem label="Bulk evaluate">
|
|
607
|
+
```bash
|
|
608
|
+
POST /_/feature-flags/evaluate
|
|
609
|
+
Content-Type: application/json
|
|
610
|
+
|
|
611
|
+
{
|
|
612
|
+
"flagNames": ["new-checkout-flow", "beta-reporting", "new-dashboard-ui"],
|
|
613
|
+
"customProperties": {
|
|
614
|
+
"experimentGroup": "b"
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
```
|
|
618
|
+
|
|
619
|
+
Evaluates multiple flags in a single request. `flagNames` is required; `customProperties` is an optional map of additional context attributes to merge into the evaluation context.
|
|
620
|
+
|
|
621
|
+
Response:
|
|
622
|
+
|
|
623
|
+
```json
|
|
624
|
+
{
|
|
625
|
+
"flags": {
|
|
626
|
+
"new-checkout-flow": true,
|
|
627
|
+
"beta-reporting": false,
|
|
628
|
+
"new-dashboard-ui": null
|
|
629
|
+
},
|
|
630
|
+
"evaluatedAt": "2026-01-10T09:00:00Z"
|
|
631
|
+
}
|
|
632
|
+
```
|
|
633
|
+
</TabItem>
|
|
634
|
+
<TabItem label="Delete flag">
|
|
635
|
+
```bash
|
|
636
|
+
DELETE /_/feature-flags/new-checkout-flow
|
|
637
|
+
```
|
|
638
|
+
|
|
639
|
+
Deletes the feature flag and all its associated value rules. This operation is permanent.
|
|
640
|
+
|
|
641
|
+
Response:
|
|
642
|
+
|
|
643
|
+
```json
|
|
644
|
+
{
|
|
645
|
+
"name": "new-checkout-flow",
|
|
646
|
+
"deletedAt": "2026-01-10T09:00:00Z",
|
|
647
|
+
"message": "Feature flag \"new-checkout-flow\" has been permanently deleted"
|
|
648
|
+
}
|
|
649
|
+
```
|
|
650
|
+
</TabItem>
|
|
651
|
+
</Tabs>
|
|
652
|
+
|
|
653
|
+
---
|
|
654
|
+
|
|
655
|
+
## Simulating Feature Flag Evaluation
|
|
656
|
+
|
|
657
|
+
The simulate endpoint (`POST /_/feature-flags/simulate/:flagName`) is a development and debugging tool that lets you test how a flag evaluates for any arbitrary context without being tied to the authenticated user.
|
|
658
|
+
|
|
659
|
+
### Evaluate vs Simulate
|
|
660
|
+
|
|
661
|
+
| Aspect | `POST /evaluate/:flagName` | `POST /simulate/:flagName` |
|
|
662
|
+
|--------|---------------------------|---------------------------|
|
|
663
|
+
| **Context source** | Authenticated user (derived server-side) | Request body `{ context: {...} }` |
|
|
664
|
+
| **Error on missing flag** | Returns `null` | Returns `FLAG_NOT_FOUND` reason |
|
|
665
|
+
| **Response detail** | Value only | Value + reason + matched rule + echoed context |
|
|
666
|
+
| **Non-visible flag** | Returns `null` | Returns `defaultValue` with `FLAG_INACTIVE` reason |
|
|
667
|
+
| **Intended use** | Production evaluation | Development / rule debugging |
|
|
668
|
+
|
|
669
|
+
### SimulationReason Values
|
|
670
|
+
|
|
671
|
+
| Reason | When returned |
|
|
672
|
+
|--------|---------------|
|
|
673
|
+
| `FLAG_NOT_FOUND` | No flag with the given name exists in the database |
|
|
674
|
+
| `FLAG_INACTIVE` | The flag exists but `visible` is `false` |
|
|
675
|
+
| `RULE_MATCHED` | A value rule's condition matched the provided context |
|
|
676
|
+
| `DEFAULT_VALUE` | The flag is visible but no rule matched; `defaultValue` is returned |
|
|
677
|
+
|
|
678
|
+
### Example — Rule Matched
|
|
679
|
+
|
|
680
|
+
```bash
|
|
681
|
+
POST /_/feature-flags/simulate/beta-reporting
|
|
682
|
+
Content-Type: application/json
|
|
683
|
+
|
|
684
|
+
{
|
|
685
|
+
"context": {
|
|
686
|
+
"username": "beta-user-1@example.com",
|
|
687
|
+
"tenant": "acme-corp"
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
```
|
|
691
|
+
|
|
692
|
+
```json
|
|
693
|
+
{
|
|
694
|
+
"flagName": "beta-reporting",
|
|
695
|
+
"active": true,
|
|
696
|
+
"value": true,
|
|
697
|
+
"reason": "RULE_MATCHED",
|
|
698
|
+
"matchedRule": {
|
|
699
|
+
"name": "beta-user-access",
|
|
700
|
+
"condition": {
|
|
701
|
+
"attribute": "user",
|
|
702
|
+
"operator": "in",
|
|
703
|
+
"value": ["beta-user-1@example.com", "beta-user-2@example.com"]
|
|
704
|
+
},
|
|
705
|
+
"value": true,
|
|
706
|
+
"rollout": null
|
|
707
|
+
},
|
|
708
|
+
"evaluatedAt": "2026-01-10T09:00:00Z",
|
|
709
|
+
"context": {
|
|
710
|
+
"username": "beta-user-1@example.com",
|
|
711
|
+
"tenant": "acme-corp"
|
|
712
|
+
}
|
|
713
|
+
}
|
|
714
|
+
```
|
|
715
|
+
|
|
716
|
+
### Example — No Rule Matched (Default Value Returned)
|
|
717
|
+
|
|
718
|
+
```bash
|
|
719
|
+
POST /_/feature-flags/simulate/beta-reporting
|
|
720
|
+
Content-Type: application/json
|
|
721
|
+
|
|
722
|
+
{
|
|
723
|
+
"context": {
|
|
724
|
+
"username": "regular-user@example.com",
|
|
725
|
+
"tenant": "acme-corp"
|
|
726
|
+
}
|
|
727
|
+
}
|
|
728
|
+
```
|
|
729
|
+
|
|
730
|
+
```json
|
|
731
|
+
{
|
|
732
|
+
"flagName": "beta-reporting",
|
|
733
|
+
"active": true,
|
|
734
|
+
"value": false,
|
|
735
|
+
"reason": "DEFAULT_VALUE",
|
|
736
|
+
"matchedRule": null,
|
|
737
|
+
"evaluatedAt": "2026-01-10T09:00:00Z",
|
|
738
|
+
"context": {
|
|
739
|
+
"username": "regular-user@example.com",
|
|
740
|
+
"tenant": "acme-corp"
|
|
741
|
+
}
|
|
742
|
+
}
|
|
743
|
+
```
|
|
744
|
+
|
|
745
|
+
<Aside type="tip">
|
|
746
|
+
Use the simulate endpoint during development to verify your targeting rules before deploying. You can test any combination of context attributes without needing to log in as different users, making it straightforward to confirm that conditions like `in`, `AND`, or percentage rollouts work as intended.
|
|
747
|
+
</Aside>
|
|
748
|
+
|
|
749
|
+
---
|
|
750
|
+
|
|
751
|
+
## Usage in React Applications
|
|
752
|
+
|
|
753
|
+
The platform provides React hooks and components for easy feature flag integration in UI applications.
|
|
754
|
+
|
|
755
|
+
### Setup
|
|
756
|
+
|
|
757
|
+
**1. Install the SDK:**
|
|
758
|
+
|
|
759
|
+
The SDK is included in the `pae-ui-react-core` package.
|
|
760
|
+
|
|
761
|
+
### Reading Flag Values with useFeatureFlag
|
|
762
|
+
|
|
763
|
+
The `useFeatureFlag` hook returns the evaluated value of a flag for the current user context. Because flags can now return any type, the hook returns `any`.
|
|
764
|
+
|
|
765
|
+
```typescript
|
|
766
|
+
import { useFeatureFlag } from '@bluealba/pae-ui-react-core';
|
|
767
|
+
|
|
768
|
+
function MyComponent() {
|
|
769
|
+
const isNewDashboardEnabled = useFeatureFlag('new-dashboard');
|
|
770
|
+
|
|
771
|
+
return (
|
|
772
|
+
<div>
|
|
773
|
+
{isNewDashboardEnabled ? (
|
|
774
|
+
<NewDashboard />
|
|
775
|
+
) : (
|
|
776
|
+
<LegacyDashboard />
|
|
777
|
+
)}
|
|
778
|
+
</div>
|
|
779
|
+
);
|
|
780
|
+
}
|
|
781
|
+
```
|
|
782
|
+
|
|
783
|
+
**Hook Signature:**
|
|
784
|
+
|
|
785
|
+
```typescript
|
|
786
|
+
function useFeatureFlag(flagName: string): any
|
|
787
|
+
```
|
|
788
|
+
|
|
789
|
+
**Parameters:**
|
|
790
|
+
- `flagName`: Name of the feature flag to evaluate
|
|
791
|
+
|
|
792
|
+
**Returns:**
|
|
793
|
+
- `any`: The evaluated flag value. For boolean flags this will be `true` or `false`; for typed flags it will be whatever value the matching rule specifies. Returns `undefined` when the flag is not present in the bootstrapped flags map.
|
|
794
|
+
|
|
795
|
+
**Example with a string-valued flag:**
|
|
796
|
+
|
|
797
|
+
```typescript
|
|
798
|
+
function ThemeSelector() {
|
|
799
|
+
const theme = useFeatureFlag('ui-theme'); // returns 'light', 'dark', 'system', or undefined
|
|
800
|
+
|
|
801
|
+
return <AppShell theme={theme ?? 'light'} />;
|
|
802
|
+
}
|
|
803
|
+
```
|
|
804
|
+
|
|
805
|
+
### Conditional Rendering with FeatureFlagGuard
|
|
806
|
+
|
|
807
|
+
`FeatureFlagGuard` renders its children only when the evaluated flag value is strictly `true`. It is designed for boolean flags used as on/off guards.
|
|
808
|
+
|
|
809
|
+
```typescript
|
|
810
|
+
import { FeatureFlagGuard } from '@bluealba/pae-ui-react-core';
|
|
811
|
+
|
|
812
|
+
function MyComponent() {
|
|
813
|
+
return (
|
|
814
|
+
<div>
|
|
815
|
+
<h1>My App</h1>
|
|
816
|
+
|
|
817
|
+
<FeatureFlagGuard flag="beta-features" fallback={<ComingSoon />}>
|
|
818
|
+
<BetaFeatures />
|
|
819
|
+
</FeatureFlagGuard>
|
|
820
|
+
|
|
821
|
+
<FeatureFlagGuard flag="premium-analytics">
|
|
822
|
+
<PremiumAnalytics />
|
|
823
|
+
</FeatureFlagGuard>
|
|
824
|
+
</div>
|
|
825
|
+
);
|
|
826
|
+
}
|
|
827
|
+
```
|
|
828
|
+
|
|
829
|
+
**Component Props:**
|
|
830
|
+
|
|
831
|
+
| Prop | Type | Required | Description |
|
|
832
|
+
|------|------|----------|-------------|
|
|
833
|
+
| `flag` | string | Yes | Name of the feature flag |
|
|
834
|
+
| `fallback` | ReactNode | No | Content to show when flag value is not `true` |
|
|
835
|
+
| `children` | ReactNode | Yes | Content to show when flag value is strictly `true` |
|
|
836
|
+
| `invert` | boolean | No | When `true`, shows children when the flag is disabled and `fallback` when enabled. Defaults to `false`. |
|
|
837
|
+
|
|
838
|
+
<Aside type="note">
|
|
839
|
+
`FeatureFlagGuard` uses a strict equality check (`=== true`). If your flag returns a truthy non-boolean value (e.g., a non-empty string), the children will not render. Use `useFeatureFlag` directly and write your own condition for non-boolean flags.
|
|
840
|
+
</Aside>
|
|
841
|
+
|
|
842
|
+
---
|
|
843
|
+
|
|
844
|
+
## Usage in NestJS Backend Services
|
|
845
|
+
|
|
846
|
+
The gateway evaluates all feature flags for the current user before forwarding any proxied request to a microservice, and injects the results as the `x-forwarded-feature-flags` header (base64-encoded JSON). The `@bluealba/pae-service-nestjs-sdk` package exposes utilities to read those flags — no additional HTTP calls required.
|
|
847
|
+
|
|
848
|
+
### Setup
|
|
849
|
+
|
|
850
|
+
Add `FeatureFlagsClientModule` to the `imports` array of the NestJS module where you need flag access:
|
|
851
|
+
|
|
852
|
+
```typescript
|
|
853
|
+
import { FeatureFlagsClientModule } from '@bluealba/pae-service-nestjs-sdk';
|
|
854
|
+
import { Module } from '@nestjs/common';
|
|
855
|
+
import { AcmeController } from './acme.controller';
|
|
856
|
+
import { AcmeService } from './acme.service';
|
|
857
|
+
|
|
858
|
+
@Module({
|
|
859
|
+
imports: [FeatureFlagsClientModule],
|
|
860
|
+
controllers: [AcmeController],
|
|
861
|
+
providers: [AcmeService],
|
|
862
|
+
})
|
|
863
|
+
export class AcmeModule {}
|
|
864
|
+
```
|
|
865
|
+
|
|
866
|
+
### Option 1 — Injectable Service
|
|
867
|
+
|
|
868
|
+
Inject `FeatureFlagsClientService` into any provider that has access to the request object:
|
|
869
|
+
|
|
870
|
+
```typescript
|
|
871
|
+
import { Injectable } from '@nestjs/common';
|
|
872
|
+
import { FeatureFlagsClientService } from '@bluealba/pae-service-nestjs-sdk';
|
|
873
|
+
import { FastifyRequest } from 'fastify';
|
|
874
|
+
|
|
875
|
+
@Injectable()
|
|
876
|
+
export class AcmeService {
|
|
877
|
+
constructor(private readonly featureFlags: FeatureFlagsClientService) {}
|
|
878
|
+
|
|
879
|
+
async getItems(req: FastifyRequest) {
|
|
880
|
+
const isNewUI = this.featureFlags.get<boolean>(req, 'new-ui') ?? false;
|
|
881
|
+
const pageSize = this.featureFlags.get<number>(req, 'items-page-size') ?? 20;
|
|
882
|
+
// ...
|
|
883
|
+
}
|
|
884
|
+
}
|
|
885
|
+
```
|
|
886
|
+
|
|
887
|
+
**API:**
|
|
888
|
+
|
|
889
|
+
| Method | Signature | Description |
|
|
890
|
+
|--------|-----------|-------------|
|
|
891
|
+
| `getAll` | `getAll(req): Record<string, any>` | Returns all evaluated flags for the request |
|
|
892
|
+
| `get` | `get<T>(req, flagName): T \| undefined` | Returns the value of a specific flag, or `undefined` if not present |
|
|
893
|
+
|
|
894
|
+
### Option 2 — Parameter Decorator in Controllers
|
|
895
|
+
|
|
896
|
+
Use `@FeatureFlags()` to bind flag values directly to controller method parameters:
|
|
897
|
+
|
|
898
|
+
```typescript
|
|
899
|
+
import { Controller, Get } from '@nestjs/common';
|
|
900
|
+
import { FeatureFlags } from '@bluealba/pae-service-nestjs-sdk';
|
|
901
|
+
|
|
902
|
+
@Controller('acme')
|
|
903
|
+
export class AcmeController {
|
|
904
|
+
@Get()
|
|
905
|
+
async getItems(@FeatureFlags('new-ui') isNewUI: boolean) {
|
|
906
|
+
// isNewUI is the evaluated value of the 'new-ui' flag for this user
|
|
907
|
+
}
|
|
908
|
+
|
|
909
|
+
@Get('debug')
|
|
910
|
+
async debugFlags(@FeatureFlags() flags: Record<string, any>) {
|
|
911
|
+
return { flags };
|
|
912
|
+
}
|
|
913
|
+
}
|
|
914
|
+
```
|
|
915
|
+
|
|
916
|
+
| Usage | Resolves to |
|
|
917
|
+
|-------|-------------|
|
|
918
|
+
| `@FeatureFlags()` | `Record<string, any>` — all evaluated flags |
|
|
919
|
+
| `@FeatureFlags('flag-name')` | `T \| undefined` — value of the named flag |
|
|
920
|
+
|
|
921
|
+
<Aside type="caution">
|
|
922
|
+
Do not use feature flags as security gates. Flags are forwarded from the gateway at request time but are not re-validated by the microservice. Use the platform's authorization system (roles and operations) for access control.
|
|
923
|
+
</Aside>
|
|
924
|
+
|
|
925
|
+
---
|
|
926
|
+
|
|
927
|
+
## Best Practices
|
|
928
|
+
|
|
929
|
+
### When to Use Feature Flags
|
|
930
|
+
|
|
931
|
+
<CardGrid stagger>
|
|
932
|
+
<Card title="New Features" icon="rocket">
|
|
933
|
+
Use release flags for new features to control rollout and enable quick rollback if issues arise.
|
|
934
|
+
</Card>
|
|
935
|
+
|
|
936
|
+
<Card title="Kill Switches" icon="warning">
|
|
937
|
+
Use kill-switch flags for critical systems that may need instant disabling. Set `visible: false` to immediately short-circuit evaluation.
|
|
938
|
+
</Card>
|
|
939
|
+
|
|
940
|
+
<Card title="Permissions" icon="lock">
|
|
941
|
+
Use permission flags to control feature access based on user role or tenant.
|
|
942
|
+
</Card>
|
|
943
|
+
</CardGrid>
|
|
944
|
+
|
|
945
|
+
**When NOT to use feature flags:**
|
|
946
|
+
- For every small code change (flag sprawl)
|
|
947
|
+
- As a replacement for configuration management
|
|
948
|
+
- For security-sensitive features that should be removed entirely
|
|
949
|
+
- When the code complexity becomes unmanageable
|
|
950
|
+
|
|
951
|
+
### Always Associate Flags with an Application
|
|
952
|
+
|
|
953
|
+
Every feature flag should be linked to the application that owns it, using `applicationId` (via API) or `applicationName` (via bootstrap). This practice:
|
|
954
|
+
|
|
955
|
+
- Keeps the Feature Flags page navigable as the platform scales
|
|
956
|
+
- Makes it immediately clear which team is responsible for a flag
|
|
957
|
+
- Enables accurate filtering in the Admin UI
|
|
958
|
+
|
|
959
|
+
### Order Value Rules from Most Specific to Least Specific
|
|
960
|
+
|
|
961
|
+
Because evaluation stops at the first matching rule, place the most targeted rules at the top of the `valueRules` array. A broad catch-all rule at the end acts as a secondary default.
|
|
962
|
+
|
|
963
|
+
```typescript
|
|
964
|
+
// Good — specific rules first, broad rule last
|
|
965
|
+
valueRules: [
|
|
966
|
+
{
|
|
967
|
+
name: 'admin-preview',
|
|
968
|
+
condition: { attribute: 'user', operator: 'eq', value: 'admin@example.com' },
|
|
969
|
+
value: 'admin-preview' // Exact user gets a special value
|
|
970
|
+
},
|
|
971
|
+
{
|
|
972
|
+
name: 'beta-access',
|
|
973
|
+
condition: { attribute: 'role', operator: 'eq', value: 'beta' },
|
|
974
|
+
value: true // Beta role gets the feature
|
|
975
|
+
}
|
|
976
|
+
// No further rules — all other users fall through to defaultValue
|
|
977
|
+
]
|
|
978
|
+
```
|
|
979
|
+
|
|
980
|
+
### Use the visible Field as a Master Switch
|
|
981
|
+
|
|
982
|
+
Set `visible: false` to immediately deactivate a flag without deleting it or clearing its value rules. This is the safest way to perform an emergency rollback — re-enabling a flag is a single field update.
|
|
983
|
+
|
|
984
|
+
### Naming Conventions
|
|
985
|
+
|
|
986
|
+
Use consistent, descriptive names for your feature flags:
|
|
987
|
+
|
|
988
|
+
**Recommended Formats:**
|
|
989
|
+
|
|
990
|
+
```
|
|
991
|
+
# Feature releases
|
|
992
|
+
new-[feature-name]
|
|
993
|
+
enable-[feature-name]
|
|
994
|
+
[feature-name]-v2
|
|
995
|
+
|
|
996
|
+
# Examples
|
|
997
|
+
new-dashboard-ui
|
|
998
|
+
enable-advanced-search
|
|
999
|
+
checkout-flow-v2
|
|
1000
|
+
|
|
1001
|
+
# Experiments
|
|
1002
|
+
[feature]-[experiment-name]
|
|
1003
|
+
ab-test-[feature]
|
|
1004
|
+
|
|
1005
|
+
# Examples
|
|
1006
|
+
checkout-express-flow
|
|
1007
|
+
ab-test-pricing-page
|
|
1008
|
+
|
|
1009
|
+
# Kill switches
|
|
1010
|
+
[system]-enabled
|
|
1011
|
+
[integration]-active
|
|
1012
|
+
|
|
1013
|
+
# Examples
|
|
1014
|
+
payment-processing-enabled
|
|
1015
|
+
analytics-tracking-active
|
|
1016
|
+
```
|
|
1017
|
+
|
|
1018
|
+
**Best Practices:**
|
|
1019
|
+
- Use kebab-case (lowercase with hyphens)
|
|
1020
|
+
- Be descriptive but concise
|
|
1021
|
+
- Include feature area in the name
|
|
1022
|
+
- Avoid generic names like `feature1` or `test`
|
|
1023
|
+
|
|
1024
|
+
---
|
|
1025
|
+
|
|
1026
|
+
## Summary
|
|
1027
|
+
|
|
1028
|
+
The Blue Alba Platform's feature flags system provides a powerful, flexible way to control feature availability in your applications. Key takeaways:
|
|
1029
|
+
|
|
1030
|
+
<CardGrid stagger>
|
|
1031
|
+
<Card title="Rules-Based Evaluation" icon="approve-check">
|
|
1032
|
+
Flags use an ordered `valueRules` array. The first matching rule wins; `defaultValue` is the fallback.
|
|
1033
|
+
</Card>
|
|
1034
|
+
|
|
1035
|
+
<Card title="Composable Conditions" icon="magnifier">
|
|
1036
|
+
Combine leaf conditions (`eq`, `neq`, `in`, `nin`) with `AND`/`OR` composites to express any targeting logic.
|
|
1037
|
+
</Card>
|
|
1038
|
+
|
|
1039
|
+
<Card title="Self-Contained" icon="rocket">
|
|
1040
|
+
Requires only PostgreSQL — no external services needed.
|
|
1041
|
+
</Card>
|
|
1042
|
+
|
|
1043
|
+
<Card title="Application-Scoped" icon="document">
|
|
1044
|
+
Associate flags with applications to keep large flag sets organized and filterable.
|
|
1045
|
+
</Card>
|
|
1046
|
+
</CardGrid>
|
|
1047
|
+
|
|
1048
|
+
<Aside type="tip">
|
|
1049
|
+
Keep in mind that you can manage the feature flags in the platform admin application. Use the Application selector at the top of the Feature Flags page to filter flags by the application that owns them.
|
|
1050
|
+
</Aside>
|
|
1051
|
+
|
|
1052
|
+
**Next Steps:**
|
|
1053
|
+
1. Create your first feature flag and assign it to an application
|
|
1054
|
+
2. Declare flags in your application's bootstrap configuration using `applicationName` and `valueRules`
|
|
1055
|
+
3. Integrate the React SDK using `useFeatureFlag` or `FeatureFlagGuard`
|
|
1056
|
+
4. In NestJS microservices, import `FeatureFlagsClientModule` and use `FeatureFlagsClientService` or `@FeatureFlags()`
|
|
1057
|
+
5. Start using feature flags to control rollouts safely
|
|
1058
|
+
|
|
1059
|
+
For questions or issues, reach out to the platform team.
|