fflip
Version:
Advanced Feature Flipping/Toggling Across the Server and Client
182 lines (136 loc) • 7.25 kB
Markdown
<a href="https://www.npmjs.com/package/fflip">
<img align="right" src="https://nodei.co/npm/fflip.png?compact=true" />
</a>
# <img src="http://fredkschott.com/img/fflipIcon2.png" /> fflip
Working on an experimental new design? Starting a closed beta? Rolling out a new feature over the next few weeks? Fa-fa-fa-flip it! __fflip__ gives you complete control over releasing new functionality to your users based on their user id, join date, membership status, and whatever else you can think of. __fflip's__ goal is to be the most powerful and extensible feature flipping/toggling module out there.
- Create __custom criteria__ to segment users & features based on your audience.
- __View & edit feature access__ in one easy place, and not scattered around your code base.
- __System-Agnostic:__ Support any database, user representation or web framework you can throw at it.
- __Extensible:__ Supports 3rd-party plugins for your favorite libraries (like [our Express integration](https://github.com/FredKSchott/fflip-express)!)
```
npm install fflip --save
```
## Getting Started
Below is a simple example that uses __fflip__ to deliver a closed beta to a fraction of users:
```javascript
// Include fflip
var fflip = require('fflip');
fflip.config({
criteria: ExampleCriteria, // defined below
features: ExampleFeatures // defined below
});
// Get all of a user's enabled features...
someFreeUser.features = fflip.getFeaturesForUser(someFreeUser);
if(someFreeUser.features.closedBeta === true) {
console.log('Welcome to the Closed Beta!');
}
// ... or just check this single feature.
if (fflip.isFeatureEnabledForUser('closedBeta', someFreeUser) === true) {
console.log('Welcome to the Closed Beta!');
}
```
### Criteria
**Criteria** are the rules that define access to different features. Each criteria takes a user object and some data as arguments, and returns true/false if the user matches that criteria. You will use these criteria to restrict/allow features for different subsets of your userbase.
```javascript
var ExampleCriteria = [
{
id: 'isPaidUser', // required
check: function(user, isPaid) { // required
return user.isPaid == isPaid;
}
},
{
id: 'percentageOfUsers',
check: function(user, percent) {
return (user.id % 100 < percent * 100);
}
},
{
id: 'allowUserIDs',
check: function(user, allowedIDs) {
return allowedIDs.indexOf(user.id) > -1;
}
}
];
```
### Features
**Features** represent some special behaviors in your application. They also define a set of criteria to test users against for each feature. When you ask fflip if a feature is enabled for some user, it will check that user against each rule/criteria, and return "true" if the user passes.
Features are described in the following way:
```javascript
var ExampleFeatures = [
{
id: 'closedBeta', // required
// if `criteria` is in an object, ALL criteria in that set must evaluate to true to enable for user
criteria: {isPaidUser: true, percentageOfUsers: 0.50}
},
{
id: 'newFeatureRollout',
// if `criteria` is in an array, ANY ONE set of criteria must evaluate to true to enable for user
criteria: [{isPaidUser: true}, {percentageOfUsers: 0.50}]
},
{
id: 'experimentalFeature',
name: 'An Experimental Feature', // user-defined properties are optional but can be used to add important metadata
description: 'Experimental feature still in development, useful for internal development', // user-defined
owner: 'Fred K. Schott <fkschott.com>', // user-defined
enabled: false, // sets the feature on or off for all users, required unless `criteria` is present instead
},
]
```
The value present for each rule is passed in as the data argument to it's criteria function. This allows you to write more general, flexible, reusable rules.
Rule sets & lists can be nested and combined. It can help to think of criteria sets as a group of `AND` operators, and lists as a set of `OR` operators.
#### Veto Criteria
If you'd like to allow wider access to your feature while still preventing a specific group of users, you can use the `$veto` property. If the `$veto` property is present on a member of a criteria list (array), and that member evaluates to false, the entire list will evaluate to false regardless of it's other members.
```javascript
{
// Enabled if user is paid OR in the lucky 50% group of other users currently using a modern browser
criteria: [{isPaidUser: true}, {percentageOfUsers: 0.50, usingModernBrowser: true}]
// Enabled if user is paid OR in the lucky 50% group of other users, BUT ONLY if using a modern browser
criteria: [{isPaidUser: true}, {percentageOfUsers: 0.50}, {usingModernBrowser: true, $veto: true}]
}
```
## Usage
- `.config(options) -> void`: Configure fflip (see below)
- `.isFeatureEnabledForUser(featureName, user) -> boolean`: Return true/false if featureName is enabled for user
- `.getFeaturesForUser(user) -> Object`: Return object of true/false for all features for user
- `.reload() -> void`: Force a reload (if loading features dynamically)
### Configuration
Configure fflip using any of the following options:
```javascript
fflip.config({
criteria: {}, // Criteria Array
features: {}, // Features Array | Function (see below)
reload: 30, // Interval for refreshing features, in seconds
});
```
### Loading Features Dynamically
fflip also accepts functions for loading features. If fflip is passed a function with no arguments it will call the function and accept the return value. To load asynchronously, pass a function that sends a features object to a callback. fflip will receive the callback and set the data accordingly. In both cases, fflip will save the function and call it again every X seconds, as set by the reload parameter.
```javascript
// Load Criteria Synchronously
var getFeaturesSync = function() {
var collection = db.collection('features');
var featuresArr = collection.find().toArray();
/* Process/Format `featuresArr` if needed (format described above) */
return featuresArr;
}
// Load Features Asynchronously
var getFeaturesAsync = function(callback) {
var collection = db.collection('features');
collection.find().toArray(function(err, featuresArr) {
/* Handle err
* Process/Format `featuresArr` if needed (format described above) */
callback(featuresArr);
});
}
fflip.config({
criteria: ExampleCriteriaObject,
features: getFeaturesAsync, // or: getFeaturesSync
reload: 60 // update available features every 60 seconds
});
```
## Integrations
As mentioned, fflip's goal is to be flexible enough to integrate with any web framework, database, or ORM. The following integrations are known to exist:
- [fflip-express](https://github.com/FredKSchott/fflip-express): Express.js integration
If you're interested in creating an integration, don't hesitate to reach out or create an issue if some functionality is missing. And if you've created an integration, please [add it](https://github.com/FredKSchott/fflip/edit/master/README.md) to the list above!
## Special Thanks
Original logo designed by <a href="http://thenounproject.com/Luboš Volkov" target="_blank">Luboš Volkov</a>