見出し画像

使われるものづくりへの近道 : ScanPaが選んだFeature Flag戦略

キャッシュレスコインパーキング ScanPa のテックリードをしている近藤 (@mtyk_15) です。
この記事では、ScanPa で Feature Flag を導入する際の工夫や効果を紹介します。


1. ScanPa について


Feature Flag の導入背景は事業によって、様々だと思います。
ScanPa についても少し説明させてください。

これまで駐車場の運営には精算機が必要でしたが、ScanPa を利用することによって QR コード 1 つで駐車場の運営を始めることができます。
そこで ScanPa は、「マンションが立つまでの遊休地を一時的に駐車場として活用できないか」というアイディアからスタートしました。

現場の様子
  • 一日に数百件の入出庫が存在する

  • 日中 ~ 夜間と 24 時間使われている

  • 日本全国に現場がある

このように変えるのが難しい「土地」とソフトウェアが混ざり合った面白い事業になっています。
今後ハードウェアとの連携などさらなる事業拡大を目指すため、今まさに将来に向けた技術的な投資をしているというフェーズです。 

2. Feature Flagとは


Feature Flag とはコードの変更をせずにソフトウェアの振る舞いを変える手法のことを指します。
もっともシンプルな実装としては

isFunctionalityEnabled := true

if isFunctionalityEnabled {
  // do something
} else {
  // do something
}

このように、isFunctionalityEnabled をトグルにして機能を出し分けます。 以下の記事を参照すると Feature Flag は Longevity (Feature Flag の寿命) と Dynamism (どれくらいの頻度で Feature Flag が変更されるか) の 2 軸でマッピングすることができます。

Feature Toggles (aka Feature Flags) より

表にまとめると、以下のようになります

Feature Flag の種類

一般的に Feature Flag というと Release Toggle や Experiment toggle が想起されますが、この記事では「コードの変更をせずにソフトウェアの振る舞いを変える手法」全般を指しています。

3. Feature Flag のない世界


少し時間をさかのぼって Feature Flag がなかった頃の話をさせてください。

3-1. 機能をまとめてリリース

リリースに含まれるコードの変更が大きくなりがちという問題がありました。 後方互換性を担保しつつ小さくリリースできれば良かったのですが、若いサービスということもありなかなか難しく。
影響範囲が大きいバックエンドの変更やフロントエンドの変更では、消極的になってしまいました … 。

3-2. ステージングで実態とは差異のあるデータでテスト

そしてこれは意外とよくあることだと思うのですが、ステージングでは気づかなかったけど本番で見るとバグに気づくということがありました。
これは色々な原因があると思いますが、弊社の中では

  • ステージングのデータが本番相当になっていないこと

  • 本番で実際に運用すると、違和感を感じる

という点が大きかったと思います。 スタートアップでリソースが限られているので、ステージング環境の整備や QA に十分な時間と人を割くことができていなかったというのが実情です。

3-3. ストレスフルなリリースに

サービスの性質上、利用者の少ない深夜帯にリリースすることが多いので、上記に加えて

  • 夜遅くて辛い

  • 万が一何かあったら明け方までかかる (社内で起きている人が少ないのでオペレーション的にも不安)

というストレスがかかります。 結果としてリリース作業はエンジニアにとって胃がキリキリするものになりました。

4. Feature Flag のある世界


先述の通りコードの変更をすることなく、ソフトウェアの振る舞いを変えたいケースが多く発生するようになりました。そこで新たに Feature Flag (Experimental Toggle, Release Toggle) を導入しました。
ちなみに先の 4 つのカテゴリに分類すると以下のような実装になっています。

ScanPa での Feature Flag の実装

この記事では中でも Unleash という Feature Flag マネジメントツールを導入
して、Experimental Toggle や Release Toggle を導入した効果を紹介します。

ちなみに色々なツールがあると思いますが、Unleash は Slack コミュニティも活発で UI や機能群も充実していたので採用しました。

(こんな感じで Next.js でうまく動作しない Isssue がありましたが、翌週には修正されてリリースされていました。)

4-1. 機能を小さい単位でプロダクションに出せる

これは導入の際に狙っていた Release toggle の効果です。
フロントエンドの変更も feature Flag で隠せるので、ビッグバンリリースにならず安心して変更を出せるようになりました。

4-2. 特定の人、組織だけに機能を公開できる

想像していたよりも Experimental toggle の効果は絶大でした。
先述の QA に十分リソースを割けないという問題も、「社内だけ先に公開して運用の中で QA してもらう」ということができるようになりました。
また、自分にだけ機能を解放して本番環境で動作検証することができるので、インフラの変更などステージングと同じ動作が担保されていないケースにも効力を発揮しました。

4-3. リリースと機能の公開・非公開を切り離せる

そして改めて「機能の公開」と「コードのリリース」を切り離すことができる偉大さを実感しました。
リリースのストレスは『コードのリリース』に起因していたことに気づき、十分に検証された『機能の公開』はむしろ達成感や喜びを伴う作業だということも実感しました。

5. ここが大事。 Feature Flag 運用の工夫


数ヶ月 Feature Flag を運用して、重要だと思った Tips を紹介します。

5-1. 分岐を最小限にする

Feature Flag の実装はやみくもにするとかなりカオスなコードになってしまいます。そこでScanPa では以下のような工夫をしています。

  • なるべく分岐を最小にする

  • Feature Flag の実装自体への依存は避ける

フロントエンドでは、Provider を提供するモジュールをつくり feature Flag の実装を隠蔽しています。

import {
  FlagProvider,
  useFlag,
  useFlags,
  useUnleashClient,
} from '@unleash/nextjs/client';
import { ComponentProps, useEffect } from 'react';

export type Feature =
  | 'scanpa-flag'

export type UnleashContext = {
  accountId?: string;
  organizationId?: string;
  parkingId?: string;
  userId?: string;
};
export type Props = {
  children: React.ReactNode;
  context?: UnleashContext;
};

export function FeatureFlagProvider(props: Props) {
  const { children, context = {} } = props;

  const config: ComponentProps<typeof FlagProvider>['config'] = {
    url: process.env.NEXT_PUBLIC_UNLEASH_URL,
    clientKey: process.env.NEXT_PUBLIC_UNLEASH_CLIENT_KEY,
    appName: process.env.NEXT_PUBLIC_UNLEASH_APP_NAME,
    environment: process.env.NEXT_PUBLIC_UNLEASH_ENVIRONMENT,
  };

  if (!config.clientKey) {
    throw new Error('Missing NEXT_PUBLIC_UNLEASH_CLIENT_KEY');
  }

  return (
    <FlagProvider
      config={{
        ...config,
        context,
      }}
    >
      {children}
    </FlagProvider>
  );
}

その上で Feature Flag の分岐が最小になるように、機能の ON / OFF が特定のコンポーネントのみに依存している場合はそれを隠すようにしたり、広い範囲に影響している場合は Organism 単位でコンポーネントをだし分けるようにします。
またバックエンドでは、ミドルウェアで feature Flag の実装を隠蔽して、実質的に handler に DI しているような構成になっています。

func FeatureFlagMiddleware() mux.MiddlewareFunc {
	err := unleash.Initialize(
		unleash.WithEnvironment(util.Environment),
		unleash.WithAppName(util.UnleashAppName),
		unleash.WithUrl(util.UnleashUrl),
		unleash.WithCustomHeaders(http.Header{"Authorization": {util.UnleashToken}}),
	)
	if err != nil {
		return func(next http.Handler) http.Handler {
			return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
				next.ServeHTTP(w, r)
			})
		}
	}

	return func(next http.Handler) http.Handler {
		return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			// NOTE: コンテキストにフラグの有効状態を格納する
			enables := map[feature_flag.FlagValue]bool{}

			for _, value := range feature_flag.Flags {
				enables[value] = unleash.IsEnabled(string(value))
			}

			newCtx := feature_flag.WithValue(r, enables)
			next.ServeHTTP(w, r.WithContext(newCtx))
		})
	}
}

5-2. 寿命を適切に見定める。しっかり寿命を守る。

Release や Experimental toggle は管理を怠ると増加してしまいます。 これは IF 文の増加を意味し、コードの可読性を著しく下げます。
Unleash ではこのあたりのマネジメントもできるようになっています。以下のように Flag ごとに最後にいつ使ったかやフラグのステータス (Initial, Pre-live, Live, Completed, Archived) を管理できます。
ScanPa では Unleash をフル活用して、寿命を超えて利用されている Feature Flag がないか、また、Feature Flag の ON / OFF を安全に管理するようにしています。

5-3. QA はしっかり

Feature Flag の数だけ IF 文がコードベース内に増加します。 そのため QA では feature Flag の種類だけ動作確認パターンが増加することになります。
弊社でも Feature Flag の組み合わせによって動作が不安定になるケースがありました。
QA は Feature Flag の数だけ煩雑になるということを理解することが重要です。

6. 実感。工夫の効果


上記の工夫はいずれも Feature Flag によるバグを出さないことに役立ちました。
なるべく条件分岐を最小にしてそれらを十分に検証してリリースすることで、Feature Flag 起因のバグを出さずに運用することができています。

Feature Flag 自体の効果は実感しやすいし、分かりやすいと思います。いかに Feature Flag をストレスなく・ミスなく運用していくかが大事だと考えています。

7. 今後の展望


今回の記事では紹介しきれなかったのですが、大きく

  • トランクベースな開発に繋がるような基盤作り

  • より攻めた開発ができるように Experiments toggle を使いこなす

の 2 点に取り組んでいます。
この辺は、また別の機会に深掘りした記事を執筆できればと思います。

8. 最後に


私たちGOGENは、技術を利用してより良い・新しい体験をユーザーを届けることに情熱を燃やしています。一緒に、ユーザーをワクワクさせるようなものづくりをしませんか?
まずはカジュアルにお話しできればと思います。興味をお持ちいただいた方、ぜひお話ししましょう !


この記事が気に入ったらサポートをしてみませんか?