-
Notifications
You must be signed in to change notification settings - Fork 5
Add sample feature flagging system to next.js template #259
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
Changes from all commits
f9767d6
353630f
6e20779
ed8be4d
4bbe4fc
3f03b05
682ba3f
4c00a33
5608096
4bbaf3d
8e4ea30
121a839
072e5c9
96a1067
22af91b
ee9ebc7
5960077
b85be42
211a22c
19bfc25
14e7f66
f3eb517
ebd85a6
5e7bbd3
d5bc09e
cc4f457
ea37495
4ef4d5f
baa0f9c
04958d5
e03f4bc
e8033ee
f8abfea
68da9d5
354b7ec
a3768f2
dcdf950
6b0d455
6e0fe93
90e4a95
e038ff4
62b1f5b
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 |
---|---|---|
|
@@ -2,3 +2,4 @@ | |
.DS_Store | ||
# Developer-specific IDE settings | ||
.vscode | ||
.env.local |
Large diffs are not rendered by default.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
import { Evidently } from "@aws-sdk/client-evidently"; | ||
|
||
/** | ||
* Class for managing feature flagging via AWS Evidently. | ||
* Class method are available for use in next.js server side code. | ||
* | ||
* https://docs.aws.amazon.com/AWSJavaScriptSDK/v3/latest/client/evidently/ | ||
* | ||
*/ | ||
export class FeatureFlagManager { | ||
client: Evidently; | ||
private _project = process.env.FEATURE_FLAGS_PROJECT; | ||
|
||
constructor() { | ||
this.client = new Evidently(); | ||
} | ||
|
||
async isFeatureEnabled(featureName: string, userId?: string) { | ||
const evalRequest = { | ||
entityId: userId, | ||
feature: featureName, | ||
project: this._project, | ||
}; | ||
|
||
let featureFlagValue = false; | ||
try { | ||
const evaluation = await this.client.evaluateFeature(evalRequest); | ||
if (evaluation && evaluation.value?.boolValue !== undefined) { | ||
featureFlagValue = evaluation.value.boolValue; | ||
console.log({ | ||
message: "Made feature flag evaluation with AWS Evidently", | ||
data: { | ||
reason: evaluation.reason, | ||
userId: userId, | ||
featureName: featureName, | ||
featureFlagValue: featureFlagValue, | ||
}, | ||
}); | ||
} | ||
} catch (e) { | ||
console.error({ | ||
message: "Error retrieving feature flag variation from AWS Evidently", | ||
data: { | ||
err: e, | ||
userId: userId, | ||
featureName: featureName, | ||
}, | ||
}); | ||
} | ||
return featureFlagValue; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
export class LocalFeatureFlagManager { | ||
async isFeatureEnabled(featureName: string, userId: string) { | ||
console.log("Using mock feature flag manager", { featureName, userId }); | ||
return Promise.resolve(false); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
import { LocalFeatureFlagManager } from "../LocalFeatureFlagManager"; | ||
import type { FlagManager } from "../setup"; | ||
|
||
export const manager: FlagManager = new LocalFeatureFlagManager(); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
import { manager } from "./setup"; | ||
|
||
export function isFeatureEnabled(feature: string, userId?: string) { | ||
return manager.isFeatureEnabled(feature, userId); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
import { FeatureFlagManager } from "./FeatureFlagManager"; | ||
import { LocalFeatureFlagManager } from "./LocalFeatureFlagManager"; | ||
|
||
export interface FlagManager { | ||
isFeatureEnabled(feature: string, userId?: string): Promise<boolean>; | ||
} | ||
|
||
export const manager: FlagManager = process.env.FEATURE_FLAGS_PROJECT | ||
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 we may still want to add 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 prefer not to do environment checks in our source code since that breaks 12 factor app principles. A couple of reasons: (1) if the logic in the code has branches based on environment variables, it means that we have less confidence that the code branch that's covered in one environment (e.g. automated test suite) would work the same as the code branch that's covered when deployed to production and. Relatedly, in terms of test coverage tools, one entire branch of code will not be covered. (2) it makes it harder to implement automated integration tests if the test suite is forced to not use the AWS flag manager. If we want to be more explicit about when we're using one flag manager type over the other without having to comment out or remove the FEATURE_FLAGS_PROJECT env var, we could always add an extra env var like FEATURE_FLAG_MANAGER_TYPE that is either "aws" or "local" e.g.
would that address the issue you mentioned without a NODE_ENV check? 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. Cross-posting from Slack: Sorry, I caused confusion with an inaccurate statement — Next.js only loads env vars into the test environment from the .env file, but it doesn’t load the vars in files like .env.development or .env.local so I think ultimately Ali your existing condition that just checks the FF project var would be enough. |
||
? new FeatureFlagManager() | ||
: new LocalFeatureFlagManager(); |
aligg marked this conversation as resolved.
Show resolved
Hide resolved
|
aligg marked this conversation as resolved.
Show resolved
Hide resolved
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
# Feature flagging | ||
|
||
- [AWS Evidently](https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/CloudWatch-Evidently.html) is used for feature flagging | ||
- For more information about the decision-making behind using Evidently, [this infra ADR is available](https://github.com/navapbc/template-infra/blob/68b2db42d06198cb070b0603e63a930db346309f/docs/decisions/infra/0010-feature-flags-system-design.md) | ||
- Additional documentation of the feature flagging solution is available in [infra docs](https://github.com/navapbc/template-infra/blob/main/docs/feature-flags.md) | ||
|
||
## How it works | ||
|
||
1. `services/feature-flags/FeatureFlagManager` provides a service layer to interact with AWS Evidently endpoints. For example, class method `isFeatureEnabled` calls out to Evidently to retrieve a feature flag value we can then return to the client | ||
1. Pages can call `isFeatureEnabled` from Next.js server side code and return the feature flag value to components as props. | ||
|
||
## Local development | ||
|
||
Out-of-the-box, local calls where `FEATURE_FLAGS_PROJECT` environment variable is unset will fall back to use `LocalFeatureFlagManager` which defaults flag values to `false`. | ||
|
||
If you want to test Evidently locally, use your AWS IAM credentials. Once you set `FEATURE_FLAGS_PROJECT` and the AWS environment variables (`AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`, and `AWS_REGION`) in `app/.env.local`, calls to Evidently will succeed. | ||
|
||
## Creating a new feature flag | ||
|
||
To create a new feature flag, update `/infra/[app_name]/app-config/main.tf`. More information available in infra repository [docs](https://github.com/navapbc/template-infra/blob/main/docs/feature-flags.md). | ||
|
||
## Toggling feature flags | ||
|
||
Toggle feature flags via the AWS Console GUI. More information [here](https://github.com/navapbc/template-infra/blob/main/docs/feature-flags.md#managing-feature-releases-and-partial-rollouts-via-aws-console). |
Uh oh!
There was an error while loading. Please reload this page.