🐥

FastAPIでFeatureFlagを使ってみる

2022/09/18に公開

はじめに

トランクベースの開発でCD(デリバリー/デプロイ)を良い感じにする方法がないか調べていたところFeatureFlagを試したくなりました。私が普段使っているWebAPIフレームワークのFastAPIでFeatureFlagを使ってみます。

fastapi-featureflags

FastAPI用に開発されたfastapi-featureflagsを使っていきます。これを使うことでFastAPIのエンドポイントからFeatureFlagの切り替えが出来ます。FeatureFlagの数がN個ある場合、何の工夫もしなければ2のN乗個の環境を用意して検証しますが、管理用のエンドポイントからFeatureFlagを切り替えることができるので一台で幾通りの環境を検証できます。開発用や検証用で費用を抑えたい場合などで活用できます。

とりあえずpipを使ってインストールします。

pip install fastapi-featureflags

実際に触ってみる

まずはFastAPIが関係ないFeatureFlagの例です。

demo.py
from fastapi_featureflags import FeatureFlags, feature_flag, feature_enabled

FeatureFlags.load_conf_from_dict(
    {
        "flag1": False,
        "flag2": True,
    }
)
print("Enabled Features:", FeatureFlags.get_features())


@feature_flag("flag1")
def flag1_enabled():
    print("Feature Should be enabled: flag1")


flag1_enabled()

if feature_enabled("flag2"):
    print("Feature Should be disabled: flag2")

FeatureFlags.enable_feature("flag1")
flag1_enabled()
$ python demo.py  
Enabled Features: {'flag1': False, 'flag2': True}
Feature Should be disabled: flag2
Feature Should be enabled: flag1

1回目のflag1_enabled()を実行した際はflag1がFalseなので実行されずスキップされました。flag1を有効にして再度flag1_enabledを実行すると動作しました。

次にFastAPIを使ったFeatureFlagの例です。

fastapi-demo.py
from fastapi import FastAPI
from fastapi_featureflags import router as ff_router
from fastapi_featureflags import FeatureFlags

FeatureFlags.load_conf_from_dict(
    {
        "flag1": False,
        "flag2": True,
    }
)
app = FastAPI()


@app.get("/")
def enabled_features():
    return FeatureFlags.get_features()


app.include_router(ff_router, prefix="/ff", tags=["FeatureFlags"])
$ pip install uvicorn
$ uvicorn fastapi-demo:app

/docsにアクセスするとSwaggerのUIから以下のAPIエンドポイントが確認できます。

  • /all: FeatureFlagsの値を取得
  • /enable/{feature_flag}: {feature_flag}の有効化
  • /disable/{feature_flag}: {feature_flag}の無効化
  • /reload: FeatureFlagsを初期状態に戻す

SwaggerUI

これらのエンドポイントを使ってFeatureFlagの切り替えをしてみます。まずは現状の値の確認から。

$ curl -X 'GET' 'http://localhost:8000/ff/all' -H 'accept: application/json'
{
  "flag1": false,
  "flag2": true
}

FeatureFlagの有効化と無効化。

$ curl -X 'GET' 'http://localhost:8000/ff/enable/flag1' -H 'accept: application/json'
{
  "feature_flag": "flag1",
  "enabled": true
}

$ curl -X 'GET' 'http://localhost:8000/ff/disable/flag2' -H 'accept: application/json'
{
  "feature_flag": "flag2",
  "enabled": false
}

$ curl -X 'GET' 'http://localhost:8000/ff/all' -H 'accept: application/json'
{
  "flag1": true,
  "flag2": false
}

flag1を有効化、flag2を無効化しました。FeatureFlagsを初期状態に戻すにはreloadのエンドポイントをたたくと出来ます。

$ curl -X 'GET' 'http://localhost:8000/ff/reload' -H 'accept: application/json'
{
  "feature_flags": {
    "flag1": false,
    "flag2": true
  },
  "reloaded": true
}

$ curl -X 'GET' 'http://localhost:8000/ff/all' -H 'accept: application/json'
{
  "flag1": false,
  "flag2": true
}

まとめ

FeatureFlagをFastAPIで使ってみました。本番環境で意図しないFeatureFlagの更新を防ぐためにapp.inclucde_router(...)を本番では実行しないであったり、include_in_schema=FalseにしてSwaggerUI上では見えないようにするのが良いかもしれません。

参考

https://martinfowler.com/articles/feature-toggles.html
https://www.optimizely.com/optimization-glossary/feature-flags/

Discussion