こんにちは。エンジニアの濱田( @hamakou108 )です。
数ヶ月前にM&Aクラウドの feature toggles の仕組みを刷新したので、今回はその内容について紹介したいと思います。
Feature toggles とは?
Feature toggles はアプリケーションのビルド時または実行中に機能の切り替えを行う仕組みです。 Feature toggles はいわゆる feature branches の戦略とは対照的に、メインのブランチに頻繁にコードを統合します。 こうすることで CI/CD による早期のフィードバックと開発中の個別機能へのアクセスを両立します。
詳細については以下のページが参考になるかと思います。
- Feature Toggles (aka Feature Flags)
- ALM Rangers - Software Development with Feature Toggles | Microsoft Docs
- Feature Toggleについて整理してみました - SRE兼スクラムマスターのブログ
刷新前の feature toggles の運用方法と問題点
M&Aクラウドはフレームワークとして Laravel と Nuxt.js を利用しており、それぞれが別の環境で稼働しています。 各環境用の環境変数はファイルで管理しており、そこに feature toggles の値を追加するような形で運用を行っていました。
しかしこの運用方法には幾つか問題がありました。
環境変数の更新からリリースまでのタイムラグ
各環境に環境変数を反映するために、わざわざファイルを更新して CircleCI のワークフローを実行する必要がありました。 この一連の作業に30分から1時間程度の時間が掛かり、リリースのタイミングのコントロールが難しい状況になっていました。 バグが混入していた場合の切り戻しでも同様の作業を行う必要があるため、 MTTR の悪化の要因の一つにもなっていました。
各環境の切り替えタイミングの差異
Laravel と Nuxt.js の動作環境が異なることから、デプロイフローもそれぞれ分かれています。 このためフロントエンドとバックエンドで feature toggle の切り替えタイミングに差異が生じてしまい、それに起因してエラーが発生することが何度かありました。
Feature toggles の機能の刷新
上述した問題点を解消するべく、以下の課題を克服する新たな feature toggles 機能を開発するミニプロジェクトが立ち上がりました。
- Feature toggles の切り替えタイミングをコントロール可能であること
- フロントエンドとバックエンドで同じタイミングで feature toggle を切り替えられること
ここからは設計上のポイントについて3つ紹介します。
コード中にマスタを定義し、 RDB で on/off を管理
環境変数による feature toggles の管理は廃止し、次のような設計を行いました。
まず RDB に feature toggles 用のテーブルを作成し、 toggle の on/off をレコードとして管理できるようにします。 レコードを追加・更新すれば toggle の on/off が即時に切り替わるようにする算段です。
もう一つの工夫として FeatureToggleName
という Enum 型のクラスを用意し、プロパティとして各 toggle の名前を持たせています。
<?php class FeatureToggleName extends BaseEnum { // 新規作成する際に定数を追加 const FOO = '2021_05_27_foo'; const BAR = '2021_05_28_bar'; const BAZ = '2021_05_29_baz'; public function __construct($value) { parent::__construct($value); if (!$this->isValidValue()) { throw new InvalidArgumentException('引数の形式は YYYY_MM_DD_name でなければなりません'); } } private function isValidValue(): bool { return (bool) preg_match('/\d{4}_\d{2}_\d{2}_.+$/', $this->rawValue()); } }
アプリケーション内では FeatureToggleName
のプロパティを feature toggles のマスタとし、ここに定義されていない toggle は存在しないものとして扱うように設計しています。
こうすることで不要になったレコードがテーブル内に残存していても、プロパティを削除しておけば UI から不要な toggle を排除できます。
また新たに追加する toggle の名前が過去に使っていたフラグの名前とたまたま重複していた場合、残存していた過去のレコードによって意図せずにフラグが on になってしまう危険があります。 これを避けるため、(現実的には)重複しないようなフラグ名を設定することをコンストラクタで強制しています。
管理画面から GUI でフラグ切り替え
M&Aクラウド社内のメンバーがアクセスできる管理画面に feature toggles 管理用のページを追加し、フラグの一覧表示および更新ができるようにしました 1 。
即時に反映されるのは勿論のこと、アプリケーション経由で更新することで監査ログも残るようになり、ガバナンスの面でも強固な仕組みとなりました。 監査ログの機能に関しては別の記事としてまとめられていますので、興味のある方はこちらもご覧ください。
フロントエンド向け API
フロントエンド向けの feature toggles 取得 API を作成しました。 Nuxt.js の Router の middleware として設定することで各ページを開いた際に API が呼び出され、 Vuex 経由で feature toggles が取得できるようになりました。
const nuxtConfig: Configuration = { // ... router: { middleware: 'fetch-feature-toggle' }, // ... }
Feature toggles の刷新後
上述した課題を克服し、先日リリースした「かんたんM&A診断」では新しい feature toggles の仕組みを使って即時リリースすることができました!
その後、開発時に想定していませんでしたが、この仕組みにはデータとロジックを同時に移行する際にメリットが得られることに気づきました。 Feature toggles も RDB で管理されているため、同一トランザクション内で移行したいデータと feature toggles のレコードを更新することで、データ移行の間にユーザーの操作が発生してデータに不整合が発生するといったケースを防止できます 2。
一方で新たな課題も浮かび上がってきました。
機能のリリース後に不要なコードを削除する際、 FeatureToggleName
のプロパティを先に消してしまうとそのフラグが off として扱われるようになるため、リリースした機能が巻き戻ってしまいます。
そのため削除の際は
- フラグを使って条件分岐を行うコードを削除する
FeatureToggleName
のプロパティを削除する
という手順を踏まなくてはなりません。 この順序を気にしなくても良いように改善することが今後の課題です。