🎏

開発生産性を上げるシンプルな仕組み、Feature Flagの使いどころ

2023/04/20に公開

はじめに

みなさんこんにちは、物流業界の価値最大化をミッションに掲げるアセンド株式会社で取締役CTOを務めている丹羽です。

私たちはレガシー産業である運送会社さんのDXを実現すべく、運送管理SaaS「ロジックス」を開発しています。運送業にまつわる業務の全てをデジタル化・プロダクト化し繋がる世界を実現しようとしています。そのためにアセンドでは開発生産性にこだわり投資をしています。

https://www.ascendlogi.co.jp/logix

そんなアセンドのプロダクトチームでは週間平均 8.20deploys/day を実現するなど、1段高い生産性を実現しています。今回はこの生産性を支える仕組みの1つである Feature Flag についてご紹介します。


(毎日SlackBotでデプロイ状況などのレポートを通知するようにしています)

Feature Flag とは?

Feature Flag とは、プロダクト開発時に新機能の公開(リリース)や変更を簡単に制御できるようにする仕組みです。コード内にフラグを埋め込むことで、機能のオン/オフをすることが簡単にできるようになります。これにより開発チームは開発中機能のコードでもマージしデプロイすることができるようになりコンフリクトを抑えられるだけでなく、変更差分が最小化でき例え障害が発生したとしてもビックバンリリースに比べれば格段に小さな影響に抑えることができます。

Feature Flagのメリット

  • 開発中の機能をマージすることができる。
    • コンフリクトを抑えられる。コンフリクト解消時の考慮を最小化できる。旧機能の改修時にも新機能の存在をコード上にも示すことができる。
    • 開発中でも細かい単位でPRを出しマージすることができる。
    • 新機能開発時でも旧機能に部分影響が出ることはあり、それを事前にリリースして問題がないことを確認することができる
  • リリース影響範囲をコントロールすることができる。
    • 小さい単位で新機能の開発をマージできるため、切り戻しも容易。
    • 一部顧客だけの先行公開など、影響範囲を最小にし段階的にリリースできる。
    • コードのマージ・デプロイタイミングと、公開(リリース)タイミングを分けることができ、公開時の作業を減らすことができる。
  • 上記を総合して、小さな変更を入れやすくなり開発生産性が向上する

Feature Flag の実装

さて、そんな開発生産性に大きな向上をもたらす Feature Flag ですが、下記の通り非常にシンプルな仕組みで実現することができます。ただの if文 ですね。

export const Page = () => {
  if (featureFlag) {
    return <NewFeature />
  } else {
    return <CurrentFeature />
  }
}

アセンドでは Feature Flag をもう少し作り込んで下記の実装で使用しています。

  • 開発環境ごとに機能のオン/オフをできるようにする
  • 場合によってはユーザーさんのテナントごとに機能のオン/オフを可能に
    • 導入当初は環境ごとのオン/オフだけでも十分ですし、テナント別制御は複雑性が増すので消極的に利用するべきです。
  • Feature名をstring型でなく固定のユニオン型とし誤字を防ぐ
    • 誤字で一部箇所だけ無効になる事故が一度ありました😇
feature-flag.ts
export type Features =
  | "NewFeature";

type Flag =
  | { flag: "on" | "off"; }
  | { flag: "some"; tenantIds: string[]; };
type EnvFlag = Record<EnvironmentType, Flag>;

const FlagDefinitions: Record<Features, EnvFlag> = {
  NewFeature: {
    local: { flag: "on" },
    development: {
      flag: "some",
      tenantIds: [
        TenantList.AscendUnsou.id,
        TenantList.DemoSales.id,
      ],
    },
    production: { flag: "off" },
  }
};

export const Feature Flags = {
  check(feature: Features, ctx: ContextType) {
    const tenantId = ctx.getTenantId()
    const flag = matchEnv(FlagDefinitions[feature]);
    switch (flag.flag) {
      case "on":
        return true;
      case "off":
        return false;
      case "some":
        return flag.tenantIds.includes(tenantId);
    }
  }
}
page.ts
export const Page = () => {
  const ctx = useContext()
  if (Feature Flags.check("NewFeature", ctx)) {
    return <NewFeature />
  } else {
    return <CurrentFeature />
  }
}

アセンドでは Full TypeScript Architecture を採用しており、バックエンドとフロントエンドで一部コードを共有することができます。 Feature Flag に関しても同様で、コード定義を共通化し同時にデプロイすることができる便利さがあります。
異なる言語のBE/FEで同時にFeatureを操作したい、高度に管理したい場合は LaunchDarkly などのツールもあるため、検討しても良いかもしれません。が、上記の通り非常なシンプルな作りだけでも有効なためまずは簡単に試してみることもお勧めします。

Feature Flag の利用事例

続いて実際にアセンドで Feature Flag を活用している場面をご紹介します。
私は Feature Flag を乱用しすぎると複雑性が増すため利用しない選択肢も持つべきとも考えていますが、やむなく利用するケースもあり今回は赤裸々に公開していますので、少しでもお役に立てればと思います。

開発中の大玉機能を隠す

先日、ロジックスに労務管理という大玉機能をリリースしました。こちらの実装期間は約4ヶ月と比較的に長くありましたが、 Feature Flag を利用して開発をしてきました。

https://prtimes.jp/main/html/rd/p/000000020.000071415.html

新画面が登場する場面での Feature Flag の利用は容易で、サイドメニューのリンクを隠すだけでも最低限実現することができます。念を記すのであれば各画面コンポーネントのトップで防御をしたり、APIのルーティングを隠すことをして良いと思います。

new-page.ts
export const Page = () => {
  const ctx = useContext()
  if (!Feature Flags.check("NewFeature", ctx)) {
    return null
  }
  // ...
  // ...
  return (<>{/*some page*/}</>)
}

また、今回の労務管理は配車管理という別の大玉機能にも組み込み、配車作業時にドライバーの労働状況の注意事項を表示し働きすぎ・労働基準法違反を防止する機能がありました。この場合でも同じ Feature Flag を利用することで、リリース時にバチっと同時に公開をすることができました。

データベース移行

アセンドではデータモデルが定まりきらない開発初期には MongoDB を利用しており、データモデルが見えてきた現在では PostgreSQL に移行しました。
この移行時にも Feature Flag を利用しており、 Repository のインタフェースを共通化した上で対象を MongoDB用と PostgreSQL用で切り替えることをしました。
テーブルごとに Feature Flag を作り、影響範囲を狭め段階的にリリースすることができ、結果として壮大なプロジェクトとなることもなく無事故で完了しました。

usecase.ts
const repository = FeatureFlags.check("Postgresqlization")
  ? this.repository
  : this.legacyRepository;
const results = await repository.findAll(ctx);

帳票出力エンジンのLambda移行

こちらも移行系ですが、データベース移行と同様にDI先を変える形で実現しました。
元の帳票出力エンジンはEC2で動いてたため同時リクエストに弱く、Lambda移行をしました。
この帳票出力エンジンではテナント(ユーザ)毎に異なる帳票を柔軟に扱える機能を持っており、テストデータだけではカバーしきれない性能の不安がありました。これに対し、以下の手順で影響範囲をコントロールしながらリリースしています。

  1. 新帳票出力エンジンのコードの組み込み(FeatureFlagで非公開)
  2. 本番環境で自社のデモテナントに限定公開し、本番環境でも最低限動くことを確認
  3. 一番簡易な帳票を持つテナントへ限定公開し、多様なデータで動くことを確認
  4. 一番複雑な帳票を持つテナントへ限定公開し、Maxケースでの動作を確認。(この時に1,2回の出力失敗を観測し、迅速に切り戻しつつリソース不足を特定しました。)
  5. 全テナントへ公開し、以降は安定運用中。

新デザインのUIの利便性を一部顧客で検証する

A/Bテストに近い観点となりますが、懇意にしていただいているユーザさんに新UIを限定公開しヒアリングすることがあります。
現場業務をされている方には慣れたUIが良いとされる方もおり、実験的なデザインで一段高い操作性を提供しなければ受け入れられづらいことがあります。そんな場面で Feature Flag を利用して一部顧客に新UIをあて何度も検証し微調整してリリースすることがあります。

利用案内が必要な一部顧客への公開を遅らせる

これはレガシー産業を相手とする SaaS ならではですが、新機能の利用案内・操作説明を要するケースがあります。担当するカスタマーサクセスから直接連絡が必要なユーザ群をヒアリングし、そのユーザを例外的に除く Feature Flag を利用して部分非公開を実現しています。
また、案内にはカスタマーサクセスから案内工数が必要なことから、ユーザを第一週、第二週のグループに分け少しづつ公開することとしました。とはいえエンジニアの工数としては Flag の対象グループを1行変えるだけで済ませることができました。

Feature Flag を利用しての失敗と注意点

当然ですが Feature Flag を利用するデメリットもあります、今回は実際に起こしてしまった失敗事例とともに注意点をご紹介します。

全開放となった Feature Flag は素早く除却する

Feature Flag 運用のあるある問題ですが、全開放になった後は古いコードが残っていても表面上は存在しない状態になるため除却を忘れることがあります。リリース後に不具合があり切り戻すケースもあるために公開と除却タイミングを合わせるべきでないことも問題に影響しています。
除却対象であるのに別担当のエンジニアがわざわざメンテナンスをして、軽いですが工数を無駄にすることがありました。
あまり洗練されていない方法ですが、Slackで月1で Feature Flag お掃除しましょうリマインダを走らせています。

Feature Flag は分岐の複雑性を生むものである

同一の機能に対して複数の Feature Flag が掛かってしまい 2 x 2 = 4 のようにテストケースの増大・観点の漏れが起き、障害を発生させてしまったことがありました。
基本的には同一対象に機能開発が被らないように案件を調整することが望ましいです。障害が発生した時は、顧客に利用案内が必要なことから一部の顧客に隠しているケースでした。この時にはカスタマーサクセスに協力を仰ぎ Feature Flag の統一を実行することで複雑性を下げました。

部分公開から部分非公開のタイミングが難しい

Feature Flag では一部のユーザに先行公開することもありますし、一部のユーザへの公開を遅らせられることもあります。この時に非公開状態をデフォルトにするのか、公開状態をデフォルトにするかでフラグの意味を変える必要があります。
非公開状態をデフォルトにしたままであったために、新規ユーザへ非公開のままになってしまうことがありました。細かい点にはなりますが NewFeature のフラグを NewFeatureClose のように意味を反転させて公開状態のデフォルトを変える注意をしてくと良いです。

終わりに

今回は開発生産性を向上させる上で、シンプルだが効果的な Feature Flag について具体事例も交えてご紹介しました。特にアセンドでは ArgoCD を利用して障害が発生しても30秒で切り戻しができるインフラ環境を整えているためFeature Flag をより有効に活用できる側面があり、この仕組みについてはまた別の記事にてご紹介できればと思います。

Feature Flag を有効に活用するためには、変更をひたすら細かく刻むテクニックや影響範囲を細かくする思考が求められます。また、アセンドは Feature Flag 以外にも開発生産性のために技術的投資をしています。

オフラインでのエンジニアミートアップイベントを開催しておりますので、ご興味のある方はぜひご参加ください!記事では触れきれなかった知見も詳らかにご紹介いたします!

■ オフラインエンジニアミートアップ:4/27(木)19:30~
全部見せます!アセンドの Full TypeScript アーキテクチャ大解剖
https://ascend.connpass.com/event/280716/

■ ユニファ・グラファー共催オンラインイベント:5/09(火)19:00~
社会課題解決型 Vertical SaaS スタートアップのCTO/VPoEが語る技術戦略とビジョン
https://unifa.connpass.com/event/280104/

また少しでもアセンドに興味が湧いた方は、私の Twitter へDMをいただければと思います、喜んで返信いたします!

アセンドプロダクトチーム

Discussion