Implementing Feature Toggling in 2024

Implementing feature flags with Bit and Bit Platform: A step-by-step guide

Eden Ella
Bits and Pieces

--

Feature flags are an integral part of any platform engineering. They offer a flexible way to manage and deploy features without altering code directly.

Feature toggles can be sorted into three types:

  1. Release toggles, which control feature release timing. These allow teams to merge code into the main branch without releasing it immediately to users.
  2. Experiment toggles, also known as A/B testing toggles, which enable teams to test new features with a subset of users.
  3. Ops toggles, which allow operations teams to turn features on or off without deploying new code, are often used to manage operational aspects.

Highly composable software built with loosely coupled Bit components makes it far easier to implement feature toggles at any granularity.

When integrating feature toggles with Bit components, the three types mentioned earlier translate into:

  1. Using certain Bit components versus not using them.
  2. Swapping Bit components with other alternative components.
  3. Making the latest version of a Bit component available only to a certain group of users while others continue to use a previous version.

Sharing a feature flag component for synchronized development and enhanced type support

We’ll start by creating an “entity component.” This will be a Bit component shared among all related teams and projects within the organization.

The shared Bit components with all available feature flags

It serves as a central record for all available feature flags, their possible statuses, and types, making it easier for different parts of the software to react appropriately to the state of each flag.

bit create entity feature-flag-entity

Our feature-flag entity will include a single feature flag: an SSO authentication mechanism. The enabled property is left undefined as it should be populated by the server controlling the toggling.

/** 
* list the available feature flags to keep teams in sync
* and provide better DevEx
*/
const availableFeatureFlags = {
sso: {
description: 'Enable login with SSO',
enabled: undefined,
},
};

export class Features {
constructor(public readonly flags: FeatureFlags) {}
// ...
static readonly availableFlags = availableFeatureFlags;

/**
* enrich the feature-flag entity with dynamic data
* returned from the server, about whether a feature is enabled
* for that specific user/in that specific time
*/
static fromApi(flagsObj: FeatureFlags) {

const configuredFeatureFlags = {};

for (let flagObjName in flagsObj) {
configuredFeatureFlags[flagObjName] = {
...Features.availableFlags[flagObjName],
enabled: flagsObj[flagObjName].enabled,
};
}

return new Features(configuredFeatureFlags);
}
}

To release this Bit component, we’ll run the following:

bit tag -m "add SSO to available feature flags"
bit export

The component is built and shared on the Bit platform. Note that, at any point, a team maintaining a service can import the Bit component to their repository, update it with new feature flags, and release a new version.

Using Ripple CI on the Bit platform, dependent components will automatically be tested, built, and released with a new version. This ensures that all teams are in sync with the latest change.

The “entity” Bit component built on Ripple CI

Implementing a basic feature flags service

For the sake of simplicity, we’ll create a basic express service that communicates to other services and frontend applications whether the feature flags are enabled or not. In this case, we’ll randomly switch between SSO and no SSO:

/**
* use the 'features' entity component to stay in-sync with the available
* feature flags, their names and their types
*/
import { Features } from '@my-org/my-scope.feature-flag-entity';

// ...

const enableFiftyPercentOfTheTime = () => Math.random() < 0.5;

router.get('/feature-flags', (req, res) => {
const features = new Features({
sso: {
enabled: enableFiftyPercentOfTheTime(),
},
});

res.send(features);
});

See the official docs to learn how to implement this service as a Bit component.

Implementing a page with a feature flag condition

A team developing a sign-in form is now able to use the new SSO feature flag in their form.

For example:

/**
* use the 'features' entity component to enrich and validate the data recieved,
* enjoy intellisense and type support.
*/
import { Features } from '@learnbit-react.feature-flag-entity';

export const SignIn = ({ children }) => {
// ...

useEffect(() => {
(async () => {
try {
const data = await fetch('path/to/feature-flags-service')
// ...
const features = Features.fromApi(data);

})();
}, []);

return (
<>
/**
* SEE THE FOLLOWING SECTIONS FOR ALTERNATIVE IMPLEMENTATIONS!
*/
</>
);
};

Real applications will obviously use a more optimized implementation of the feature-flags data fetching.

We now have several alternatives to implementing the sign-in form with the SSO feature flag condition.

Update the sign-in form to render an SSO button conditionally

In this alternative, the team developing the sign-in form decides to update their sign-in form component to render the OSS option conditionally, based on the data received from the feature-flags service:

const ConditionalSso = () => {
if (!features.flags.sso.enabled) return null;
return (
<>
<Text>
<Link href="#">Connect with SSO</Link>
</Text>
<Separator />
</>
);

export function SignInForm() {
return (
<div>
// ...
<div className={styles.linksSection}>
<ConditionalSso />
// ...
</div>
);
}

Update the page to swap between components conditionally

In this alternative, the team developing the sign-in page returns one of two different Bit components. One with SSO and another without SSO (in this specific case, the ‘extended sign-in form’ will most probably be composed of the base sign-in form):

import { SignInForm } from '@my-org.my-scope/forms/sign-in';
import { ExtendedSignInForm} from '@my-org.my-scope/forms/extended-sign-in';

export function SignInPage() {
if (features.flags.sso.enabled) return <ExtendedSignInForm />;
return <SignInForm />
);
}

Update the page to swap between different versions of the same Bit component conditionally

This option uses different versions of same Bit component. One of this version will most probably include a pre-release tag. For example:

bit tag forms/sign-in --unmodified -m "add SSO option" --increment prerelease --prerelease-id beta

Which results in the following component ID: my-org.my-scope/forms/sign-in@0.0.2-beta.0 and correspondingly, the following package name: @my-org/my-scope.forms.sign-in .

To use these two versions in an existing project, we’ll first add “dependency aliases” or “package aliases” to our package.json :

{
"dependencies": {
"@my-org/my-scope.forms.sign-in": "0.0.1",
"@my-org/my-scope.forms.sign-in-sso": "npm:@my-org/my-scope.forms/sign-in@0.0.2-beta.0",
}

We can now use both versions in the same file:

import { SignInForm } from "@my-org/my-scope.forms.sign-in";
import { SignInForm as SignInFormWithSso } from "npm:@my-org/my-scope.forms.sign-in@0.0.2-beta.0"

export function SignInPage() {
if (features.flags.sso.enabled) return <SignInFormWithSso />;
return <SignInForm />
);
}

For example, see these 2 versions of the same Bit component. One is a stable release whilethe other is a pre-release:

Two versions of the sign-in form. One is a stable release (no SSO) while the other is a prerelase (with SSO)

To learn more about using Bit platform for your platform engineering see the Bit platform.

Platform engineering using Bit platform

--

--