Skip to content

Commit c00b129

Browse files
explain targeting context accessor
1 parent 8a535ef commit c00b129

File tree

2 files changed

+79
-2
lines changed

2 files changed

+79
-2
lines changed

articles/azure-app-configuration/feature-management-javascript-reference.md

Lines changed: 78 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -449,6 +449,83 @@ When defining an audience, users and groups can be excluded from the audience. E
449449

450450
In the above example, the feature is enabled for users named `Jeff` and `Alicia`. It's also enabled for users in the group named `Ring0`. However, if the user is named `Mark`, the feature is disabled, regardless of if they are in the group `Ring0` or not. Exclusions take priority over the rest of the targeting filter.
451451

452+
### Targeting in a Web Application
453+
454+
An example web application that uses the targeting feature filter is available in this [example](https://github.com/microsoft/FeatureManagement-JavaScript/tree/preview/examples/express-app) project.
455+
456+
#### Ambient targeting context
457+
458+
In web applications, especially those with multiple components or layers, passing targeting context (`userId` and `groups`) to every feature flag check can become cumbersome and repetitive. This scenario is referred to as "ambient targeting context," where the user identity information is already available in the application context (such as in session data or authentication context) but needs to be accessible to feature management evaluations throughout the application.
459+
460+
The library provides a solution through the `targetingContextAccessor` pattern. Instead of explicitly passing the targeting context with each `isEnabled` or `getVariant` call, you can provide a function that knows how to retrieve the current user's targeting information from your application's context:
461+
462+
```typescript
463+
import { FeatureManager, ConfigurationObjectFeatureFlagProvider } from "@microsoft/feature-management";
464+
465+
// Create a targeting context accessor that uses your application's auth system
466+
const targetingContextAccessor = {
467+
getTargetingContext: () => {
468+
// In a web application, this might access request context or session data
469+
// This is just an example - implement based on your application's architecture
470+
return {
471+
userId: getCurrentUserId(), // Your function to get current user
472+
groups: getUserGroups() // Your function to get user groups
473+
};
474+
}
475+
};
476+
477+
// Configure the feature manager with the accessor
478+
const featureManager = new FeatureManager(featureProvider, {
479+
targetingContextAccessor: targetingContextAccessor
480+
});
481+
482+
// Now you can call isEnabled without explicitly providing targeting context
483+
// The feature manager will use the accessor to get the current user context
484+
const isBetaEnabled = await featureManager.isEnabled("Beta");
485+
```
486+
487+
This pattern is particularly useful in server-side web applications where user context may be available in a request scope or in client applications where user identity is managed centrally.
488+
489+
#### Using AsyncLocalStorage for request context
490+
491+
One common challenge when implementing the targeting context accessor pattern is maintaining request context throughout an asynchronous call chain. In Node.js web applications, user identity information is typically available in the request object, but becomes inaccessible once you enter asynchronous operations.
492+
493+
Node.js provides [`AsyncLocalStorage`](https://nodejs.org/api/async_context.html#class-asynclocalstorage) from the `async_hooks` module to solve this problem. It creates a store that persists across asynchronous operations within the same logical "context" - perfect for maintaining request data throughout the entire request lifecycle.
494+
495+
Here's how to implement a targeting context accessor using AsyncLocalStorage in an express application:
496+
497+
```typescript
498+
import { AsyncLocalStorage } from "async_hooks";
499+
import express from "express";
500+
501+
const requestAccessor = new AsyncLocalStorage();
502+
503+
const app = express();
504+
// Middleware to store request context
505+
app.use((req, res, next) => {
506+
// Store the request in AsyncLocalStorage for this request chain
507+
requestAccessor.run(req, () => {
508+
next();
509+
});
510+
});
511+
512+
// Create targeting context accessor that retrieves user data from the current request
513+
const targetingContextAccessor = {
514+
getTargetingContext: () => {
515+
// Get the current request from AsyncLocalStorage
516+
const request = requestContext.getStore();
517+
if (!request) {
518+
return undefined; // Return undefined if there's no current request
519+
}
520+
// Extract user data from request (from session, auth token, etc.)
521+
return {
522+
userId: request.user?.id,
523+
groups: request.user?.groups || []
524+
};
525+
}
526+
};
527+
```
528+
452529
## Variants
453530

454531
When new features are added to an application, there may come a time when a feature has multiple different proposed design options. A common solution for deciding on a design is some form of A/B testing, which involves providing a different version of the feature to different segments of the user base and choosing a version based on user interaction. In this library, this functionality is enabled by representing different configurations of a feature with variants.
@@ -457,7 +534,7 @@ Variants enable a feature flag to become more than a simple on/off flag. A varia
457534

458535
### Getting a variant with targeting context
459536

460-
For each feature, a variant can be retrieved using the `FeatureManager`'s `getVariant` method. The variant assignment is dependent on the user currently being evaluated, and that information is obtained from the targeting context you passed in.
537+
For each feature, a variant can be retrieved using the `FeatureManager`'s `getVariant` method. The variant assignment is dependent on the user currently being evaluated, and that information is obtained from the targeting context you passed in. If you have registered a targeting context accessor to the `FeatureManager`, the targeting context will be automatically retrieved from it. But you can still override it by manually passing targeting context when calling `getVariant`.
461538

462539
```typescript
463540
const variant = await featureManager.getVariant("MyVariantFeatureFlag", { userId: "Sam" });

articles/azure-app-configuration/feature-management-overview.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ Feature | .NET | Spring | Python | JavaScript
3939
------- | ---- | ------ | ------ | ----------
4040
Targeting Filter | [GA](./feature-management-dotnet-reference.md#targeting) | GA | [GA](./feature-management-python-reference.md#targeting) | [GA](./feature-management-javascript-reference.md#targeting)
4141
Targeting Exclusion | [GA](./feature-management-dotnet-reference.md#targeting-exclusion) | GA | [GA](./feature-management-python-reference.md#targeting-exclusion) | [GA](./feature-management-javascript-reference.md#targeting-exclusion)
42-
Ambient Targeting | [GA](./feature-management-dotnet-reference.md#targeting-in-a-web-application) | WIP | WIP | WIP
42+
Ambient Targeting | [GA](./feature-management-dotnet-reference.md#targeting-in-a-web-application) | WIP | WIP | Preview
4343
Time Window Filter | [GA](./feature-management-dotnet-reference.md#microsofttimewindow) | GA | [GA](./feature-management-python-reference.md#microsofttimewindow) | [GA](./feature-management-javascript-reference.md#microsofttimewindow)
4444
Recurring Time Window | [GA](./feature-management-dotnet-reference.md#microsofttimewindow) | GA | WIP | WIP
4545
Custom Feature Filter | [GA](./feature-management-dotnet-reference.md#implementing-a-feature-filter) | GA | [GA](./feature-management-python-reference.md#implementing-a-feature-filter) | [GA](./feature-management-javascript-reference.md#implementing-a-feature-filter)

0 commit comments

Comments
 (0)