@sourceloop/feature-toggle-service

A service that provides feature toggle details

Usage no npm install needed!

<script type="module">
  import sourceloopFeatureToggleService from 'https://cdn.skypack.dev/@sourceloop/feature-toggle-service';
</script>

README

@sourceloop/feature-toggle-service

LoopBack

npm

node-current (scoped)

npm (prod) dependency version (scoped)

Overview

Microservice that provides functionality to maintain feature flags at various levels. Initial support for system level, tenant level and user level is provided. We are using UNEASH to achieve the toggle functionality here.

Working and Flow

This service provides method level decorators - @featuresFlag that takes an array of provider keys as metadata and verifies if the feature flags are enabled or disabled, it uses an AND operator to check for multiple features. Read more about creating loopback-4 decorators. To check if a feature is enabled or not add the following decorator over a controller method @featuresFlag({features: [StrategyBindings.TENANT_FEATURE]}) and if you want to skip all the feature checks - @featuresFlag({features: ['*']}) will allow, irrespective even if any feature is disabled. Initial implementation for system level, tenant level and user level feature flag is provided, if you want to add any custom feature flag all you need to do is

// A provider that implements FeatureInterface
export class SystemFeatureProvider implements Provider<FeatureInterface> {
  constructor(
    @inject(UNLEASH_CONST)
    private readonly unleashConst: Unleash,
  ) {}
  value(): FeatureInterface {
    return () => {
      return this.unleashConst.isEnabled('system-feature');
    };
  }
}
// Define a key of the type FeatureInterface
export const SYSTEM_FEATURE =
  BindingKey.create<FeatureInterface>('sf.system.feature');

// bind the Provider to the key
this.bind(SYSTEM_FEATURE).toProvider(SystemFeatureProvider);

// pass the key to the decorator
@featuresFlag({features: [StrategyBindings.TENANT_FEATURE,StrategyBindings.SYSTEM_FEATURE]})

Installation

npm i @sourceloop/feature-toggle-service

Usage

Unleash Admin UI Setup

Follow the steps to setup unleash locally ,make sure all the pre requisits as done. Provide correct database config values. Once the admin console is up generate the API Token. Token is used to initialise unleash in your application.

Service Setup

  • Create a new Loopback4 Application (If you don't have one already) lb4 testapp
  • Install the service - npm i @sourceloop/feature-toggle-service
  • Set up the environment variables
  • Run the migrations. (this will create the features at system, tenant and user level and their strategies)
  • Add the FeatureToggleServiceComponent to your Loopback4 Application (in application.ts).
    // add controllers to your application (optional)
    this.bind(ToggleServiceBindings.Config).to({
        bindControllers: true,
        useCustomSequence: false
    });
    // import the FeatureToggleServiceComponent
    import {FeatureToggleServiceComponent} from '@sourceloop/feature-toggle-service';
    // add Component for FeatureToggleService
    this.component(FeatureToggleServiceComponent);
    
  • Set up a Loopback4 Datasource with dataSourceName property set to FeatureToggleDbName. You can see an example datasource here.
  • Bind any of the custom providers you need.
  • Create new custom strategies as an when required.
  • Initialize unleash and the strategies within your Loopback4 Application and bind it to the unleash constant key UNLEASH_CONST (in application.ts).
    const unleash = require('unleash-client');
    unleash.initialize({
      url: 'app-url',
      appName: 'my-node-name',
      environment: process.env.APP_ENV,
      customHeaders: {Authorization: 'key'},
      strategies: [new TenantStrategy(), new UserStrategy()],
    });
    this.bind(UNLEASH_CONST).to(unleash);
    
  • Start the application npm start

Environment Variables

Connect to the unleash database setup above.

Name Required Default Value Description
NODE_ENV Y Node environment value, i.e. dev, test, prod
LOG_LEVEL Y Log level value, i.e. error, warn, info, verbose, debug
DB_HOST Y Hostname for the database server.
DB_PORT Y Port for the database server.
DB_USER Y User for the database.
DB_PASSWORD Y Password for the database user.
DB_DATABASE Y Database to connect to on the database server.
DB_SCHEMA Y Database schema used for the data source. In PostgreSQL, this will be public unless a schema is made explicitly for the service.
JWT_SECRET Y Symmetric signing key of the JWT token.
JWT_ISSUER Y Issuer of the JWT token.

Setting up a DataSource

Here is a sample Implementation DataSource implementation using environment variables and PostgreSQL as the data source.

import {inject, lifeCycleObserver, LifeCycleObserver} from '@loopback/core';
import {juggler} from '@loopback/repository';
import {FeatureToggleDbName} from '@sourceloop/authentication-service';

const config = {
  name: FeatureToggleDbName,
  connector: 'postgresql',
  url: '',
  host: process.env.DB_HOST,
  port: process.env.DB_PORT,
  user: process.env.DB_USER,
  password: process.env.DB_PASSWORD,
  database: process.env.DB_DATABASE,
  schema: process.env.DB_SCHEMA,
};

@lifeCycleObserver('datasource')
export class FeatureToggleDbDataSource
  extends juggler.DataSource
  implements LifeCycleObserver
{
  static dataSourceName = FeatureToggleDbName;
  static readonly defaultConfig = config;

  constructor(
    @inject('datasources.config.feature', {optional: true})
    dsConfig: object = config,
  ) {
    super(dsConfig);
  }
}

Migrations

The migrations use db-migrate with db-migrate-pg driver for migrations, so you will have to install these packages to use auto-migration. Please note that if you are using some pre-existing migrations or database, they may be effected. In such scenario, it is advised that you copy the migration files in your project root, using the SOURCELOOP_MIGRATION_COPY env variables. You can customize or cherry-pick the migrations in the copied files according to your specific requirements and then apply them to the DB.

Strategies

The open source unleash allows us to create our custom strategies that the features can have, giving us a free hand in implementaion. Read more about strategies. Extend to the base Strategy class override the isEnabled() method and also whenever a new strategy is added it needs to be mentioned while we initialize unleash in our application. We have created our custom strategies like Tenant and User.

unleash.initialize({
  strategies: [new TenantStrategy(), new UserStrategy()],
});

API Documentation

Common Headers

Authorization: Bearer where is a JWT token signed using JWT issuer and secret. Content-Type: application/json in the response and in request if the API method is NOT GET

Common Request path Parameters

{version}: Defines the API Version

Common Responses

200: Successful Response. Response body varies w.r.t API 401: Unauthorized: The JWT token is missing or invalid 403: Forbidden : Not allowed to execute the concerned API 404: Entity Not Found 400: Bad Request (Error message varies w.r.t API) 201: No content: Empty Response

API's Details

Visit the OpenAPI spec docs