トレタ開発者ブログ

飲食店向け予約/顧客台帳サービス「トレタ」、モバイルオーダー「トレタO/X」などを運営するトレタの開発メンバーによるブログです。

FeatureFlagの運用パターン -トレタのテックトーク

こんにちは、トレタ VPoEの北川です。 9月に入りましたが残暑が厳しすぎます…

テックトークとは

隔週に一回開催し、当番の発表者が最近の気になる技術情報であったり、業務する中での技術的な学びを社内に発信する場です。 全体30分の前半はLT(ライトニングトーク)、後半はその内容について参加者内で議論、という形式で行なっています。

今回のテックトーク

今回の発表は自分、北川の担当でした。

先日行なったエンジニアイベントでの質問の中にFeatureFlagをどう運用しているのか、という質問をいくつかいただいたので、今回の発表では弊社内でのFeatureFlagの使い方について発表しました。

FeatureFlagと一口にいってもその用途・使い方は様々なので、まずはそれらを整理しながら社内のいろいろなプロジェクト内での使われ方を分類して紹介しています。

FeatureFlagとは

まずFeatureFlag(フィーチャーフラグ)とは、特定の機能を動的にオン・オフするための仕組みです。アジャイル開発や継続的デリバリー(Continuous Delivery)のプラクティスとして、頻繁に機能をリリースしながらシステムの安定性を保つために、Facebook、Googleなどの企業で利用されてきた手法です。

FeatureFlagを使うユースケース

FeatureFlagを使う目的は様々です。いろいろなユースケース、目的を達成するための手段としてFeatureFlagが利用されます。一般的な利用目的を以下にまとめてみました。

  • 段階的リリース(Gradual Rollout) 新機能を一部のユーザーにのみ提供し、問題がないことを確認しながら徐々に全ユーザーに展開する。これにより、システムの安定性を保ちつつ、フィードバックを受けて調整することが可能です。

  • A/Bテスト 異なるバージョンの機能をユーザーごとに切り替えて提供し、どちらがより効果的かを比較するテストに利用できます。例えば、異なるUIデザインを一部のユーザーに試し、コンバージョン率を測定する場合などです。

  • カナリアリリース(Canary Release) 新機能をまず小規模なユーザーグループに提供し、そのパフォーマンスや安定性を観察して問題がないことを確認した後、全ユーザーにリリースする手法です。

  • リスク軽減 重要な機能を導入する際に、万が一不具合が発生した場合でも、即座にその機能を無効化できるようにして、システム全体に悪影響を与えるリスクを軽減します。

  • 特定のユーザー層への限定機能提供 プレミアムユーザーやベータテスターなど、特定のユーザー層だけに新しい機能を提供することができます。これにより、ユーザーに応じたカスタマイズされた体験を提供できます。

  • デプロイとリリースの分離 新機能のコードをあらかじめデプロイしておき、特定のタイミングで機能を有効化することで、デプロイとリリースを分離し、リリースのタイミングをより柔軟に管理することができます。

FeatureFlagの導入パターン

次に、特定の目的にFeatureFlagを使って解決しようとした際に、FeatureFlagの導入パターンも複数あります。こちらも一般的なパターンを以下にまとめてみました。

それぞれのパターンにはPros&Consがあり、どのパターンが適しているかは目的やサービスの規模、システムの非機能要件などによって変わってきます。

1. コード埋め込みパターン

概要

FeatureFlagのロジックをコード内に直接埋め込むパターンです。条件分岐を使用して、特定の機能を有効化または無効化します。

特徴

  • シンプルで導入が容易。
  • 開発者が直接コード内でフラグを制御できる。
  • 設定の変更にはコードの修正と再デプロイが必要。
  • 設定はコード内にあるため、外部依存がない。
  • 大規模なシステムではフラグが増えると管理が複雑になる。

ユースケース

  • 小規模なプロジェクトや、一度しか使用しない一時的なフラグ。

2. 環境変数パターン

概要

FeatureFlagの状態を環境変数として管理するパターンです。環境ごとに異なる値を設定でき、コードから環境変数を参照して機能を制御します。

特徴

  • デプロイ時に設定を変更可能。
  • 環境ごとに異なる設定を簡単に持てる(開発環境、本番環境など)。
  • 一時的なフラグなら良いが、長期間のフラグ管理には向いていない。

ユースケース

  • デプロイごとに簡単に設定を変更する必要がある場合。
  • 環境に応じて異なる設定を適用したい場合。

3. データベース連携パターン

概要

FeatureFlagの状態をデータベースに保存し、アプリケーションがその情報を参照してフラグを評価するパターンです。フラグの状態はデータベースから動的に取得されます。

特徴

  • フラグの状態を永続化できる。
  • フラグの状態変更が即座に反映され、アプリケーションの再デプロイが不要。
  • データベースに保存するため、設定変更の柔軟性が高い。
  • フラグの変更履歴や状態の追跡が可能。
  • 高度なフィーチャーフラグの管理が可能(例: ユーザーごとのカスタマイズ)。
  • データベースのパフォーマンスに依存するため、アクセス頻度が高い場合は最適化が必要。

ユースケース

  • ユーザーごとの個別設定や、複雑なフラグ管理が必要な大規模なアプリケーション。
  • フィーチャーフラグの状態や履歴を保持したい場合。

4. 分離パターン

概要

FeatureFlagを管理するコンポーネントを、アプリケーションコードとは別に管理するパターンです。設定ファイルや専用のコンフィグ管理システムを使ってフラグを管理します。

特徴

  • 設定ファイルを用いてフラグの状態を管理。
  • フラグはアプリケーションの外部に保存され、アプリケーションはその状態を参照して機能を制御。
  • コードベースから設定を切り離せるため、変更が柔軟。
  • 変更が即座に反映される。
  • 複数のアプリケーションやサービス間で統一したフラグ管理が可能。
  • 外部設定を管理するためのインフラが必要。
  • 複雑な設定管理が必要になることがある。

ユースケース

  • 大規模なシステムで、複数のアプリケーションやサービス間で統一的なFeatureFlag管理が必要な場合。
  • 設定変更を頻繁に行いたいが、アプリケーションの再デプロイを避けたい場合。

5. 外部サービスパターン

概要

FeatureFlag管理の外部サービス(LaunchDarkly、Flagsmith、Split.ioなど)を利用するパターンです。フラグの状態は外部サービス上で管理され、アプリケーションはAPIを通じてその状態を取得します。

特徴

  • 外部サービスがFeatureFlagの状態を一元管理。
  • 各種プラットフォームでフラグを統合管理可能。
  • フラグ管理に特化したツールを使用するため、スケーラビリティが高い。
  • リアルタイムでフラグの変更が反映され、ユーザーごとの柔軟な制御が可能。
  • 分析ツールやA/Bテスト機能が組み込まれていることが多い。
  • 外部サービスへの依存が生じ、サービス障害や遅延が発生するリスクがある。
  • コストがかかる(特に大規模なアプリケーションでは料金が高くなることがある)。

ユースケース

  • 大規模なマイクロサービスアーキテクチャや、複数のプラットフォームでFeatureFlagを統一的に管理する必要がある場合。
  • FeatureFlagの運用管理や分析を外部に委託したい場合

トレタでのFeatureFlagを使っている実例

さて、弊社でのFeatureFlagの導入事例についての紹介です。

上記でまとめたパターンごとに紹介していきます。

コード埋め込みパターンの例

{
  "USE_CALENDAR": true,
  "IS_FULLSCREEN": false,
}

弊社では設定画面のプロダクトにてコード埋め込みパターンでのFeatureFlag運用を行っています。 FeatureFlagを使う目的としては、クライアントアプリ向けのAPIとのその設定画面のリリースタイミングをずらすために利用しています。

新機能を出す際に、まずはAPIを先行してリリースし、利用するクライアントアプリ側が対応の後に設定画面をリリース、という流れとなります。 切り替えを行うのはデプロイを伴っても構わないため、一番簡易な手段としてコード埋め込みでFeatureFlagを扱っています。

環境変数パターンの例

export const isExtraColorPattern = (organizationId: OrganizationId): boolean => {
  const organizationIdsEnv = process.env.EXTRA_COLOR_PATTERN_ORGANIZATION_IDS;
  if (!organizationIdsEnv) {
    throw new ProcessEnvNotFoundError(500, "EXTRA_COLOR_PATTERN environment not set");
  }
  const organizationIds = organizationIdsEnv.split(",");
  return organizationIds.includes(organizationId);
};

上記のコードは、組織が特定のIDの場合にのみ特別なカラーパターンを利用できるようにするための判定式です。 利用可能な組織IDをEXTRA_COLOR_PATTERN_ORGANIZATION_IDS というkeyの環境変数に設定しています。 環境変数なので文字列しか設定できないため、カンマ区切りで配列を表現しています。

PoCであったり個社向けの対応に利用する用途なので、値の変更頻度は高くないため値の変更時にデプロイが必要になっても問題にはなりません。

データベース連携型の例

type Item {
  id: ID!
  name: Name!
  createdAt: Timestamp!
  updatedAt: Timestamp!
  ...
  metadata: KV  
}

データベースにFeatureFlagを設定する場合にはmetadata といったようなプロパティ名のKey-Value型のフィールドを用意して利用しています。 metadata のkeyの定義はDBスキーマ上は行っていないので必要応じてFeatureFlagのkeyと値を自由に追加・削除できます。

こちらは値の変更時にデプロイが不要なため、上記の環境変数パターンに比べて変更頻度が高い場合に利用しています

分離型の例

{
  "organiaztionIds": [
    "clilazd3oidfx0mm2akbmf1dj3",
    "cl3d3jpoklpx9jlkmlass31smpo"
 }

こちらは分離型として、静的ファイルに分離するという手法を取りました。

Githubで専用リポジトリを作り、/public にjsonファイルを配置しており、Vercelでプロジェクトを配信することで運用しています。 構成としてはあまり手間がかからないのと、Githubで管理されているため変更履歴が残るのとブランチを切り替えることで本番用と開発用で分けられるのが良いところです。

用途としては、リプレイスを行なったシステムに対しての移行をフラグ管理しています。 移行が終わっていない組織のIDをリストに入れておき、該当する組織は各アプリケーションではリプレイス前のシステムを利用します。 移行が済んだ組織はリストから外していき、リストが空になったらこの仕組みごと削除する、という流れになります。

複数のアプリケーションがその組織の移行状況を確認する必要があったため特定のアプリケーションではない場所で管理したかったのと、 最終的には不要になるため削除する時に後が残らず、また一覧性もあることが望ましかったため、この手法を取りました。

FeatureFlagを消すタイミング

FeatureFlagがそもそも永続化されるか一時的なものかは、パターンとあわせて利用するユースケースによります。 コード埋め込みパターンや環境変数パターンは比較的に短期利用で採用することが多いため、基本的には不要になった時点で削除を行います。

ただ、実際のところでゆうと消し忘れることは多いです。 フラグをオンにするタイミングで消すといいのですが、大体のケースはオンにして様子見てから消す、とするとそのまま放置されることが多いです。 この辺は運用ルールとして定めても守れらないと意味がないので、自動的に守られるような仕組みが必要ですが今のところそこまで至っていないのが現状です。

まとめ

長くなったので上記の内容のまとめです。 私見でのマトリクスを作ったので、FeatureFlagを使う際の判断の参考にぜひご活用ください。

  • FeatureFlagには、複数のユースケースやそれに適したアプローチがある。
  • どのアプローチでFeatureFlagを実装するかはユースケースに応じて、以下の観点で選択するとよい。
    • 外部依存度
      • レイテンシ
      • 障害点
    • 変更容易性
      • 変更にデプロイが必要でも問題ないか
    • 設定柔軟性
      • 環境にごとに切り替えたいか
      • 特定の店舗への設定か
    • 管理のしやすさ
      • 一覧性
      • 消しやすさ
コード埋め込み 環境変数型 データベース連携 分離 外部サービス
外部依存度 × ×
変更容易性 × ×
環境ごとの切り替え
設定柔軟性 ×
管理のしやすさ × ×
コスト ×

TORETA TECH UPDATE #1 イベント後記

こんにちは、トレタ VPoEの北川です。

先日、トレタ主催のエンジニアイベント「TORETA TECH UPDATE #1 -飲食を支えるフロントエンド」を開催しました。 ご参加いただいた皆様、大変ありがとうございました。 また、台風の影響によりオフラインの開催からオンラインの開催へ急遽切り替えさせていただき、参加予定だった方にはご迷惑をおかけして大変申し訳ございませんでした。

toreta.connpass.com

今回のイベントはトレタとしてコロナ禍以降にイベントを行う機会が減って、約5年振りのイベント開催でした。 自分としてもイベントを企画するのは今回が初めてだったので、今回のイベントを振り返りつつ、イベント開催をしてみようと考えている方向けに参考になりそうなことを書き記しておこうと思います。

集客の悩み

企画が動き始めたのは約3ヶ月前の6月頃で、経緯としてはこちらの記事で書いております。 当日までの準備で一番苦労した点は集客でした。

今回は採用につなげたいという目的もあったので、オフライン開催として開催の1ヶ月前にConnpassにイベント掲載をしました。 その日に登録いただいた参加者は5名程度。 イベント開催をよくやっている方からいただいたアドバイスとしては、参加者が集まるピークはイベント掲載時とまで言われていました。 弊社はまだまだ知名度も低いので集客力がないのは承知でしたが、正直これはまずい…ということで掲載内容を知り合いなどに見てもらい改善点を検討しました。

タイトルのキャッチーさ

今回のタイトルは「TORETA TECH UPDATE #1 - 飲食を支えるフロントエンド」ですが、たしかに指摘されてから見ると何のイベントかわからないのと興味も引きづらいです。 フロントエンドという部分で大まかなターゲットは分かりますが、どういうテーマかが見えてこません。 「飲食」という部分もエンジニアの興味を引くワードとしては厳しいです。 なのでこのタイトルではConnpassで見かけてくれた人をアトラクトすることはできず、実際に掲載日に応募いただいた方の経路はSNS経由でした。

改善点としては例えば、「Next.jsでのリアーキテクト」など、具体的な内容、技術要素、日頃抱えている課題に直結するなど、タイトルを見てわかるのが良いのかと痛感しました。 タイトルを途中から変えるのは憚られたので今回はそのままにしましたが、次回以降は改善しようと思います。

オフライン勉強会文化の移り変わり

これは20代の若い方からの意見としていただきましたが、オフラインのみしかない勉強会やイベントは人を誘ってでないと1人では行きづらい、とのことでした。

これについては自分の感覚と大きくギャップを感じましたが、コロナ禍以前を知らない人たちからするとオフラインでの勉強会は参加者目線ではハードルが高く、オフラインの場合でも同時にオンライン参加がないとハードルが高くて参加しづらいとのことでした。

弊社も勤務はフルリモートな環境なので、この為に外出するのは腰が重い、業務都合で時間が取れなそうならオンライン参加にしたい、という気持ちはたしかにわかります。 今回は思いがけず台風10号の影響でオンライン開催に途中で急遽切り替えましたが、おかげでオンライン参加でたくさん応募いただくことができ、最終的には35名の参加まで募ることができました。

当日の振り返り

当日は2名のトークと、その後にQ&Aという構成で行いました。

フロントエンドエンジニアの武市さんの登壇資料 speakerdeck.com

クレスウェア株式会社の奥野さんの登壇資料 speakerdeck.com

Q&AにはSli.doを利用したところ、登壇中に20件の質問をいただくことができQ&Aが非常に盛り上がりました。 いただいた質問をいくつかピックアップして紹介します。これらいただいた質問に関してはこのブログ内で順次回答していこうと思います。

  • AppRouter移行の作業にはどれくらいかかりましたか?
  • AppRouter移行後の効果はどれほどありましたか?
  • PagerRouter時代のコンポーネントとAppRouter移行後のRSCとが混在している状態での認知負荷などはありますか?
  • FeatureFlagの管理方法はどうしていますか?利用しなくなったら消しますか?
  • フロントエンドのテストはどの粒度でやていますか?ツールは何を使っていますか?
  • Next.jsのデプロイ先の構成やサーバーコストを知りたいです
  • リリースサイクルやQA体制はどうしていますか?

やはり実際にやってみてどうだったか、運用してみてどうだったか、といった事例の詳細に関心をいただけたかと思います。 ウチはこうやってるけどヨソではどうしてます?というのは私達もよく気になることなので、これからもウチではこうやってるよ!というのを今後も発信し続けていこうと思います。

次回の開催

今回のタイトルを「TORETA TECH UPDATE #1」としたので、「#2」も近々やりたいと考えています。テーマはまだ何も決まっていませんが、「フロントエンド x 〇〇」をテーマに何かできないか検討中です。いただいた質問やアンケート回答を見ていると「テスト」であったり「プロジェクト進行」みたいな部分をもっと知りたいという声をいただいたので検討させていただこうと思います。

ぜひ次回開催時の際にも沢山のご参加をお待ちしております。

マイクロサービス・リアーキテクト

こんにちは、トレタ VPoEの北川です。

先月にモバイルオーダー「トレタO/X」の新機能として、NECモバイルPOS連携をリリースしました。今回の記事ではその時の開発を振り返り、開発の裏側について紹介しようと思います。

toreta.in

外部POSとの連携

モバイルオーダーと切っても切れない関係にあるのが「POSレジ」です。POSレジとはいわゆる会計のレジのことです。

トレタO/Xではモバイルオーダー上から会計ができるオンライン決済と、内製のモバイルPOSであるO/X-POSでの対面決済が行えるようになっています。 とはいえ、多くの飲食店には既にPOSレジが入っているケースが大半で、キャッシュドロワー付きの筐体を導入している場合は多額の初期費用をかけているため、原価償却もありPOSレジを入れ替えるという判断はとても難しいです。

そのためトレタO/Xの導入を検討していただいている飲食店様からは、既に利用しているPOSレジと連携してくれないか、という声をたくさんいただいておりました。 そこで外部POSとの連携開発を行い、第1弾としてNECモバイルPOSとの連携のリリースに至ったわけです。

データ連携のアプローチ

POSレジではお会計をするものなので、伝票データを持っています。伝票データは請求金額だけではなくもちろん注文内容も含んでおり、注文を入れるための商品マスター情報をPOSレジは持っています。 つまり、モバイルオーダーとしてPOSレジと連携するということは、データのマスター情報を外部POSレジ側に委ねることを意味します。 そこで、トレタO/XとPOSレジ間での連携のアプローチは2つ考えられます。

1.モバイルオーダーで受けた注文データ等を随時POS側に送る

このアプローチは既存のシステムの構成を保ったまま、連携部分を追加しやすいというメリットがありますが、デメリットはやはり分散トランザクションとなってしまう点です。注文データなのでデータロスやタイムラグは業務要件においてクリティカルな問題となり許容できるものではありません。

2.モバイルオーダー側は注文データ等を保持せず、POSレジ側のAPIにに注文を送る

1のアプローチが非機能要件的に難しかったため、今回はこちらの方式を取りました。店舗が利用しているPOSが内製のOX-POSか、外部POSかで注文の送り先を切り分ます。

マイクロサービスの差し替え

2のアプローチをとると、トレタO/Xのアーキテクチャは以下の図のようになります。緑の部分がもともとのトレタO/Xのマイクロサービス群です。内製POSを使う場合と外部POSを使う場合で利用するマイクロサービス群は大きく分かれます。 共通で利用するのはモバイルオーダーに表示するためのメニュー管理システムと、管理システム用のアカウントの認証・認可システムです。 そして、外部POSを利用する場合は注文システムやオンライン決済システムなど今までコアなデータを扱っていたシステム群は切り離されます。

これほどダイナミックなマイクロサービスの繋ぎ合わせはマイクロサービスで当初設計した時には想定していませんでしたが、実際にこれが行えたのはマイクロサービスである利点かと思っています。ドメインごとに疎結合にしていたおかげで、必要なシステムだけを取捨選択することができています。

実際に、外部POS連携を企画してからこの形に設計し、実装は約3ヶ月で行うことができました。修正箇所がほぼフロントエンド側だけで済んだ点が大きいです。

ここでとつぜんPRですが、フロントエンド内でいかに分岐してそれぞれのパターンと整合性をとったか、という話は来週行われるTORETA TECH UPDATE のイベントでテックリードを務めた奥野さんから詳細に語っていただきます!

toreta.connpass.com

この先の発展

今回はNECモバイルPOS連携の初回リリースを目指したので、もちろん行き届いていない課題点はいくつかあります。 特に商品マスターがトレタO/X用のメニュー管理システム内と、外部POSシステムが持っている商品マスターで二重管理となってしまっています。

トレタO/Xのコンセプトは商品の魅力を最大限に表現して注文体験を向上させることを大きな価値としているため、POSレジ側が持っている商品マスターのデータ構造だけでは足りずに多くの付加情報を持たせられる必要があります。そのため商品マスターをどちらかのみにすることは難しいため、それぞれのマスターデータをいかにスムーズに同期させるかが次の課題となっています。

また、外部POS連携第1弾と前段で書いた通り、他の外部POSとも今後積極的に繋げていく予定です。これをやるにはフロントエンド側で切り替えしていくには限界がくるため、POS連携用のシステムが必要になってきます。各POSレジが提供しているAPIやデータ構造をいかに抽象化していくか、こちらも次の大きな挑戦です。

次のプレスリリースが出せたタイミングで、また振り返って開発の内部を紹介をしようと思いますので、乞うご期待ください。

© Toreta, Inc.

Powered by Hatena Blog