[REPORT] From Vision to Code: A Guide to Aligning Business Strategy with Software Development Goals is published!
GET IT here

Unlocking Agile Potential with GrowthBook and Feature Flags

readtime
Last updated on
November 6, 2024

A QUICK SUMMARY – FOR THE BUSY ONES

Feature flags - our implementation process

  1. Decision to adopt feature flags
  • Influenced by industry practices and internal project needs.
  • Aim to align with trunk-based development principles.
  1. Key implementation considerations:
  • Frontend-backend interaction.
  • Efficient flag management.
  • Future project adaptability.
  1. Tool selection: GrowthBook
  • Features: User/permission management, flag creation/modification, environment management, comprehensive documentation, SDK integration, self-hosted capability.
  1. Technical setup:
  • Docker container setup for GrowthBook and MongoDB.
  • Administration panel setup for environment configuration and flag management.
  1. Practical application:
  • React implementation: GrowthBook integration, feature flag usage.
  • NestJS implementation: Feature flag service, dependency injection, and application in metrics calculation.
  1. Conclusion:
  • Feature flags are definitely a powerful tool that makes it easier and safer to deliver new features.

Scroll down to the whole article to get deeper into each step of feature flags implementation. 

TABLE OF CONTENTS

Unlocking Agile Potential with GrowthBook and Feature Flags

Feature flags - enhancement for development teams

In the realm of contemporary software development, the ability to promptly respond to user feedback, stay in line with market trends, and adapt to evolving requirements holds immense significance. 

This is precisely where feature flags come into the picture. 

Feature flags, also commonly referred to as feature toggles, are a programming technique that empowers developers to activate or deactivate specific features without the need to deploy new code. This approach provides developers with flexibility, reduces potential risks, and facilitates the process of experimenting with new functionalities.

Feature flags: the context

Before we delve into the implementation details, let's take a step back and grasp the larger context about feature flags

In a rapidly evolving technological landscape, user preferences can change unexpectedly, and market demands may take sudden turns. Feature flags emerge as a strategic tool that empowers development teams to quickly adapt to these changes. 

By decoupling the release of features from code deployments, you achieve the capability to make real-time decisions regarding which users have access to specific features. This provides a smooth user experience and facilitates an iterative approach to development.

Imagine being able to test new features on a subset of users before a full-scale launch, while collecting invaluable feedback and metrics along the way. This controlled strategy allows you to make data-driven decisions, improve features and avoid potential pitfalls. What's more, feature flags make A/B testing easier, where different versions of a feature are compared to identify the one that resonates most effectively with users.

From a technical point of view, function flags encourage a more modular and maintainable code base. By encapsulating new features behind flags, you can reduce the complexity of code merging, isolate possible problems and ensure that unfinished or unstable features do not disrupt the user experience.

Our project: the context

We wanted to try out and play with feature flags within an internal project, without customer involvement. 

This controlled environment provided a safe space to experiment, learn, and improve our skills, all without pressure from external stakeholders. It acted as an invaluable testing arena where we could thoroughly understand the concepts and mechanics of feature flags, preparing the ground for their eventual integration into our customer-facing projects.

Why did we decide to try feature flags?

Our decision to try out feature flags was driven by several factors. 

In particular, we observed how prominent industry leaders and corporations were skillfully using feature flags to optimize their development processes, which sparked our curiosity about the potential benefits they could offer our own projects.

Additionally, our goal to follow trunk-based development principles heavily influenced our choice. 

Using feature flags aligned perfectly with our aim of continuously integrating new code into the main branch. By hiding unfinished features from users, we could smoothly add code to the main branch without causing major disruptions and without compromising the user experience.

Starting to implement the feature flags: requirements

The implementation of feature flags introduced essential requirements to ensure smooth integration. 

These included:

  • interaction between frontend and backend, 
  • efficient management 
  • and adaptability to future projects.

Our approach prioritized the retrieval of flags from both the frontend and backend. This dynamic control mechanism made it easy to simply activate or deactivate features.

The administration panel played a key role in enabling the management of flags.

For implementation, we decided to host the solution independently in a Docker container. This decision gave us more control and increased security during the deployment process

Considering cost-effectiveness was key, which led us to evaluate different tools and technologies to provide the best-fit solution.

Feature flag management with GrowthBook

Our choses solution for feature flag management was GrowthBook.

It’s a feature flagging and experimentation platform that allows for managing feature rollouts and conducting experiments.

Why did we choose GrowthBook?

User and permission management

GrowthBook allows us to effectively manage users and their permissions, ensuring appropriate access levels for different team members.

Feature flag creation and modification

With GrowthBook, we can easily create and modify feature flags, enabling controlled releases and targeted customization of features.

Environment management

GrowthBook supports the creation and management of different environments, facilitating seamless testing and deployment processes.

Well-documented documentation

GrowthBook provides comprehensive and well-documented resources, making it easier for our team to understand and utilize the tool effectively.

Easy integration of SDKs

GrowthBook offers straightforward integration with software development kits (SDKs), simplifying the process of incorporating the tool into our existing infrastructure.

Self-hosted capability

GrowthBook allows us to host and manage the tool internally, giving us more control and ensuring data privacy and security.

Technical setup using Docker containers for GrowthBook and MongoDB

By default, Growthbook stores its data in a mongo database. We use the bitnami image because it comes with a basic configuration out of the box.

The admin panel and all core functions come directly from the Growthbook image.

We also use proxy image. This speeds up synchronization when flags are added or switched, e.g. flag switching is immediately reflected in our application.

mongo:
    image: docker.io/bitnami/mongodb:6.0
    restart: always
    env_file:
      - ./.env
    volumes:
      - 'mongodb_master_data:/bitnami/mongodb'
    ports:
      - 27017:27017

growthbook:
    container_name: "growthbook"
    image: "growthbook/growthbook:latest"
    ports:
      - "4000:3000"
      - "4100:3100"
    depends_on:
      - mongo
    env_file:
      - ./.env
    volumes:
      - uploads:/usr/local/src/app/packages/back-end/uploads

  proxy:
    container_name: "growthbook_proxy"
    image: "growthbook/proxy:latest"
    ports:
      - "4300:3300"
    depends_on:
      - growthbook
    env_file:
      - ./.env

volumes:
  uploads:
  mongodb_master_data:
    driver: local
mongo:
    image: docker.io/bitnami/mongodb:6.0
    restart: always
    env_file:
      - ./.env
    volumes:
      - 'mongodb_master_data:/bitnami/mongodb'
    ports:
      - 27017:27017

growthbook:
    container_name: "growthbook"
    image: "growthbook/growthbook:latest"
    ports:
      - "4000:3000"
      - "4100:3100"
    depends_on:
      - mongo
    env_file:
      - ./.env
    volumes:
      - uploads:/usr/local/src/app/packages/back-end/uploads

  proxy:
    container_name: "growthbook_proxy"
    image: "growthbook/proxy:latest"
    ports:
      - "4300:3300"
    depends_on:
      - growthbook
    env_file:
      - ./.env

volumes:
  uploads:
  mongodb_master_data:
    driver: local

Functionalities of the administration panel for managing environments and SDKs

Let's take a closer look at the administration panel and go through the functionalities we use.

To start using the feature flags, the first step is to properly configure the environments

In our case, we decided on three environments: a production environment, a dedicated environment for testing purposes and all activities performed during the continuous integration process, and a staging environment.

Feature flags implementation - environments setup

After configuring the environments, the next task is to configure the SDKs. Each SDK corresponds to a specific environment.

Feature flags implementation - configuring the SDKs

And finally - flags. For our test case, let's create a boolean flag to test a new method for calculating the lead time distribution metric. Each flag must be assigned to at least one environment where toggling will be possible.

Feature flags implementation

React implementation

First, for better DX, let's create an enum with the feature flags that are available in our applications and packages:

export enum FEATURE_FLAG {
  FEATURE_FLAG = 'new-ltd-mertric-calculation',
}

To use a Growthbook in a React application, we need to create an instance of the Growthbook and pass it to the context provider that wraps our application. In addition, our application needs to know what flags are available at any given time and if their state changes, this should be reflected in the application. This is why loadFeatures is used in the useEffect hook with the autoRefresh option.

import { GrowthBook, GrowthBookProvider } from "@growthbook/growthbook-react";

const App = () => {
    const gb = new GrowthBook({
        apiHost: import.meta.env.VITE_FEATURE_FLAGS_API_HOST,
        clientKey: import.meta.env.VITE_FEATURE_FLAGS_CLIENT_KEY,
        enableDevMode: import.meta.env.DEV,
      });

      useEffect(() => {
        gb.loadFeatures({ autoRefresh: true });
      }, []);

    return (
        <GrowthBookProvider growthbook={gb}>
            // rest of stuff
        </GrowthBookProvider>
    );
};

To check if the flag is toggled, use useFeatureIsOn hook. This hook will return a Boolean value based on the state of the flag. Then we can render a new version of the metric or do whatever is required.

import { useFeatureIsOn } from "@growthbook/growthbook-react";

const FriendlyComponent = () => {
  const isNewLtdMetric = useFeatureIsOn(FEATURE_FLAG.NEW_LTD);

  return isNewLtdMetric ? <New/> : <Old/>
};

NestJS implementation

On the backend in the metrics tool, we use NestJS. Let's dive into the feature flag module.

First of all, create a token to manage dependency injection.

export const FEATURE_FLAG_TOKEN = Symbol('FEATURE_FLAG_TOKEN');

This token is used to create FeatureFlagService , which returns a configured instance of Growthbook.

Note that this service can be request scoped. It is not, because we have modules that are used outside the request scope. Using the request scope module in other modules with different scopes will break them.

export type FeatureFlagService = GrowthBook;

export const featureFlagProvider = {
  provide: FEATURE_FLAG_TOKEN,
  useFactory: async (
    configService: ConfigService,
  ): Promise<FeatureFlagService> => {
    const gb = new GrowthBook({
      apiHost: configService.get<string>('FEATURE_FLAGS_API_HOST')!,
      clientKey: configService.get<string>('FEATURE_FLAGS_CLIENT_KEY')!,
      enableDevMode: true,
    });

    await gb.loadFeatures();

    return gb;
  },
  inject: [ConfigService],
};

Finally, let's add this provider to the module as follows.

@Module({
  providers: [featureFlagProvider],
  exports: [featureFlagProvider],
})

To use such a service, we simply need to inject a token into another provider.

export class LeadTimeMetric {
  constructor(
    @Inject(FEATURE_FLAG_TOKEN)
    private readonly featureFlagService: FeatureFlagService,
  ){}

  calculate(...) {
    if (this.featureFlagService.isOn(FEATURE_FLAG.NEW_LTD)) {
      return; // new version of metric to be calculated
    }

    return; // old version of metric to be calculated
  }
}

Summary

Feature flags are definitely a powerful tool that makes it easier and safer to deliver new features. It takes time to get used to this new way of implementing new functionality. It is important to note that we have only scratched the surface of this tool's potential. I encourage you to consider using feature flags. In a landscape where adaptability and responsiveness are most important, feature flags offer a path that is worth exploring.

Frequently Asked Questions

No items found.

Our promise

Every year, Brainhub helps 750,000+ founders, leaders and software engineers make smart tech decisions. We earn that trust by openly sharing our insights based on practical software engineering experience.

Authors

Krystian Otto
github
JavaScript Software Engineer

Full-stack JavaScript developer with 2+ years of experience. Enthusiast of JavaScript and TypeScript.

Krystian Otto
github
JavaScript Software Engineer

Full-stack JavaScript developer with 2+ years of experience. Enthusiast of JavaScript and TypeScript.

Read next

No items found...