LINEヤフー Tech Blog

LINEヤフー株式会社のサービスを支える、技術・開発文化を発信しています。

LINE STOREにおけるOpenFeatureを用いた新しいフィーチャーフラグシステムの開発(インターンレポート)

はじめに

はじめまして。東京大学工学部化学生命工学科3年の入佐啓士です。

8月18日から8週間、LINEヤフー株式会社の就業型インターンに参加させていただきました。私が配属されたのは、LINEスタンプ・着せかえ・絵文字関連のバックエンド開発を行うチームで、Java/Kotlinをメイン言語として開発を行っており、LINE STOREというサービスの開発に関わらせていただきました。

今回の記事では、私がインターン期間中にどのような開発を行ったのか、どんな経験をしたのかなどを紹介します。

LINE STOREとは?

私が関わったサービスのLINE STOREについて説明します。LINEアプリ内のスタンプショップでスタンプや絵文字などを購入できますが、Webブラウザでも購入でき、こちらをLINE STOREと呼びます。

LINE STORE

背景&課題

それでは、私がメインタスクとして取り組んだ「OpenFeatureを用いた新しいフィーチャーフラグシステムの開発」について説明します。フィーチャーフラグ(以下、フラグ)とは、プロダクト開発時に新機能の公開や変更を簡単に制御できるようにする仕組みで、プログラムをデプロイし直すことなく、スイッチのように特定の機能をオン/オフできるメカニズムのことです。

私たちのチームでは、1つのリポジトリで複数のプロジェクトを管理するモノレポ(Monorepo)を採用しています。フラグの管理については、LINEヤフーのオープンソースソフトウェア(以下、OSS)であるCentral Dogmaというファイル管理システムを共通で使用していますが、フラグの実装方法がプロジェクトによって異なっていました。

具体的には、以下の2つの方法が存在していました:

  1. PlanOut4jというライブラリを用いてYAMLでフラグを実装、管理(参考:https://speakerdeck.com/policemankh/implementation-and-operation-of-server-side-release-with-feature-flag
  2. シンプルなJSONファイルでフラグを実装、管理

このように実装が統一されていないことに加えて、前者のPlanOut4jは数年間メンテナンスされておらず、内部で使用されているNashorn JavaScriptエンジンがJava 11以降非推奨になっているという問題がありました。また、後者の方法では、ボイラープレートコードをフラグごとに実装する必要があるという課題もありました。

これらの課題を解決するために、「新しいフラグ用のライブラリを用いて、リポジトリ内で共通のモジュールを作成し、実装の統一を図ること」が私のメインタスクでした。

OpenFeatureとは?

フラグ用のライブラリとして新しく採用を検討したOpenFeatureとは、フラグの管理方法に関わらず、共通のインターフェースで扱うことができるライブラリです。OpenFeature SDKとしてフラグ関連のインターフェースを提供しており、これらの仕様に則ることでさまざまな便利機能が使えるだけでなく、独自にフラグの管理方法をカスタマイズすることもできます。

OpenFeatureの概念図
出典:OpenFeature公式ドキュメント

アーキテクチャ図

OpenFeatureを使ったフラグ管理フローを理解しやすくするために、最終的なアーキテクチャ図と、開発者がフラグを編集してから各アプリケーションに反映されるまでのプロセスを紹介します。

  1. 開発者がGUIでフラグを編集し、Central Dogmaサーバーへ保存する
  2. CentralDogmaOpenFeatureRepositoryがCentral Dogmaサーバーとフラグの同期をする
  3. 各アプリケーションがFeatureFlagServiceを通して評価リクエストをOpenFeatureProviderに送る
  4. OpenFeatureResolverがキー(フラグ名)に基づいて、設定をCentralDogmaOpenFeatureRepositoryから取得する
  5. OpenFeatureResolverがキーに基づいてフラグ評価ロジックをFeatureFlagServiceから取得する
  6. OpenFeatureResolverがフラグを評価する
  7. OpenFeatureProviderがフラグの評価値をFeatureFlagServiceに返す
  8. 各アプリケーションにフラグ評価値を返す

アーキテクチャ図

後ほど詳しく解説しますが、それぞれを簡単に説明すると、

  • FeatureFlagService:各アプリケーションで使用するフラグのための共通モジュール
  • OpenFeatureProvider:フラグ共通モジュールと管理サービスをつなぐ役割で、OpenFeature SDKのプロバイダクラスを継承している
  • OpenFeatureResolver:実際のフラグ評価を行う
  • CentralDogmaOpenFeatureRepository:Central Dogmaサーバーと同期してフラグを取得する

これらはそれぞれ、OpenFeatureのアーキテクチャに準拠してEvaluation API、Provider、Flag Management Systemの3つに分類されます。

やったこと

実際に開発をするにあたって、どのようなことを行ったのかについて紹介します。

全体のアーキテクチャ検討

OpenFeatureを導入するにあたって、まず既存システムとの統合方法や全体的な設計方針を検討しました。

主な検討事項は以下の4つの観点に分けて考えました:

  1. Central Dogmaでのフラグ管理:既にチームで使用しているCentral Dogmaを引き続きフラグ管理システムとして活用し、OpenFeatureのProvider経由でアクセスする設計にしました。

  2. Resolverでのフラグ解釈と評価:フラグの解釈と評価を行うResolverを独自に定義しました。フラグの設定ファイルの解釈を行い、フラグの評価を実行することに役割を分離することで、フラグの設定ファイルの記述ルールが変わったときに柔軟に対応できるようにしました。

  3. Providerでの抽象化:将来的にCentral Dogma以外のフラグ管理システムに移行する可能性も考慮し、OpenFeatureのProvider抽象化を活用することで、フラグ管理システムを差し替え可能な設計にしました。

  4. フラグを利用する共通モジュール:リポジトリ内で共通して利用できるよう、FeatureFlagServiceを共通モジュールとして切り出し、各アプリケーションで利用できる設計にしました。

ファイル保存形式の決定

フラグを使用するには、オン/オフや条件などを記述するための設定ファイルを作成する必要があります。LINE STOREチームでは、PlanOut4jを使っているため、ファイル形式としてはYAMLでフラグを管理しています。

まず決める必要があるのは、フラグの設定ファイルをどのようなファイル形式で保存するかです。候補としてはYAMLとJSONの2つが挙げられました。YAMLの利点はコメントや改行が可能で可読性が高いことです。しかし、YAMLのパーサーはPlanOut4jなどのライブラリに依存してしまうため、スキーマを変更するのが難しく、ファイルが冗長になりやすいという問題があります。

一方、JSONでは設定の読み込みルールを簡単に変えられるため、柔軟にフラグ設定ファイルをカスタマイズでき、よりシンプルに記述できます。ただし、改行やコメントができないという制約があります。

結論として、今回はファイル保存形式としてJSONを採用しました。スキーマの変更による、簡潔かつライブラリに依存しない設定ファイルのカスタマイズが可能な点を重視したためです。また、コメントができない問題については、JSONCを用いたり、スキーマにコメント用のフィールドを追加したりすることで解決可能と判断しました。

フラグの評価ロジックの定義方法の決定

次に検討したのは、フラグの評価ロジックをどのように定義するかです。単純なオン/オフの場合は、設定ファイルにenabled: trueのように書くだけで済みますが、条件付きのロジックが必要な場合(例:アクセス元が日本の場合のみオン)は、設定ファイルまたはアプリケーション側にロジックを記述する必要がありました。

候補は2つありました。1つ目は、CELというGoogleが開発した式言語を利用して、設定ファイル内でJSONのプロパティとして評価ロジックを記述する方法です。2つ目は、評価ロジックはアプリケーション側に記述し、評価に必要な変数のみを設定ファイル内に持たせる方法です。

1つ目の設定ファイル内で評価ロジックを記述する方法は、1つのファイル内でフラグの評価値とロジックを確認できる利点がある一方、CELの記法を覚える学習コストや、条件文などを書くと冗長になりやすい、編集時に構文エラーが発生する可能性などの欠点もあります。

2つ目のアプリケーション側にロジックを記述する方法は、開発者が慣れ親しんだJavaで記述できる利点がありますが、実際のフラグを記述したファイルとコード間において、スキーマのバリデーションが難しいというデメリットがあります。

結果的に、評価ロジックをアプリケーション側に記述する方法を採用しました。CELの記法が冗長になりやすいこと、そして多くの開発者が慣れ親しんだJavaで記述する方が開発者体験が良いと判断したためです。

アプリケーション側の各モジュールの作成

最後に、OpenFeatureのアーキテクチャ仕様に従って、主にFlag Management System、Provider、Evaluation APIの3種類を実装しました。

Flag Management System

今回は、フラグの管理方法としてCentral Dogmaを利用しました。LINE STOREチームではアプリケーションとは別にCentral Dogmaサーバーを構築しており、そのサーバーとフラグ設定ファイルを同期するためのモジュールとしてCentralDogmaOpenFeatureRepositoryを作成しました。

このモジュールはRxJavaを用いて、外部のCentral Dogmaサーバーからフラグ設定ファイルを非同期で読み込みます。サーバーの変更を検知できるので、常に最新の設定ファイルを取得できます。このモジュール内で、フラグ設定JSONファイルをパースして、FeatureFlagクラスとしてアプリケーション内でフラグをマッピングします。

Provider

Providerは、OpenFeatureのEvaluation APIと裏側のFlag Management Systemをつなぐ役割を担います。OpenFeature SDKが提供するEventProviderクラスを継承したOpenFeatureProviderを作成しました。このEventProviderを継承するメリットは2つあります。

1つ目は、このクラスを継承していれば、裏側のFlag Management Systemが変わっても共通のEvaluation APIから呼び出せることです。今回はCentral Dogmaをフラグ管理に使用していますが、今後他のサービスに移行する際もProviderを差し替えるだけで済むため、移行コストを削減できます。

2つ目は、OpenFeatureの機能であるHooksが利用できることです。Providerに準拠することで、フラグ評価の前後やエラー発生時などに、OpenFeature SDKのHookクラスを継承したHookを簡単に挿入できます。これによりフラグ評価のメトリクスやロギングなどを簡単に導入できるという利点があります。

また、Providerと併せてOpenFeatureResolverを作成しました。このクラスは実際のフラグ評価を担当し、フラグ設定ファイルの内容と、呼び出し元からEvaluation APIを通して渡されたコンテキストおよびロジックを利用してフラグ評価を行います。

Evaluation API

OpenFeature SDKはClientクラスを提供します。このクライアントのメソッドに対してフラグのキーを指定することで、フラグの評価値を取得できます。今回はこのクライアントをラップするFeatureFlagServiceクラスを作成しました。このクラスを使用して各アプリケーションでフラグの評価値を取得します。

フラグの呼び出し方法は主に2つあります。1つ目はフラグキーのみを指定する方法で、これは単純なフラグのオン/オフの場合に使用します。2つ目はフラグキーと共にEvaluationContextCustomEvaluationを渡す方法です。

EvaluationContextはOpenFeature SDKが提供するクラスで、フラグ評価に必要な情報(例: login, countryなどのユーザー情報)を扱います。CustomEvaluationは、フラグ設定ファイルに定義された変数とEvaluationContextを用いてフラグ評価を行うための独自の関数インターフェースです。呼び出し関数をオーバーロードすることで、これら2つのフラグ評価方法を共通の関数で呼び出せるようにしました。

使用例

単純なフラグのオン/オフの場合

Central Dogmaでのファイル名がフラグのキーとなります。enabledの値によってフラグをオン/オフします。

// ファイル名: my-flag-simple.json
{ "enabled": true }

各アプリケーションでFeatureFlagServiceをインジェクトすることでフラグを以下のように呼び出せます。

public class MyService {
    @Autowired
    private FeatureFlagService featureFlagService;

    public void someMethod() {
        if (featureFlagService.getBooleanValue("my-flag-simple")) {
            // logic when the flag is enabled
        }
    }
}

ロジックを定義する場合

単純なオン/オフに加えて、フラグ切り替えの条件を設定したい場合、その条件分岐に必要な変数をvariablesというフィールドに設定します。variablesには、フラグの評価ロジックに必要な変数を定義します。この場合、ユーザーのcountryがJPの場合にフラグをオンにしたいため、下のように記述します。

// ファイル名: my-flag-country.json
{
    "enabled": true,
    "variables": {
        "country": "JP"
    }
}

各呼び出し元では、EvaluationContextCustomEvaluationを作成し、それをFeatureFlagServiceに渡してフラグの評価を行います。

public class MyService {
    @Autowired
    private FeatureFlagService featureFlagService;

    public void someMethod() {
        final EvaluationContext evaluationContext = featureFlagService.buildEvaluationContext();
        final CustomEvaluation myCustomEvaluation = (flag, context) -> {
            // evaluation logic
            Map<String, Object> vars = flag.getVariables();
            return vars.get("country").equals(context.getValue("country").asString());
        };
        
        if (featureFlagService.getBooleanValue("my-flag-country", evaluationContext, myCustomEvaluation)) {
            // logic when the flag is enabled
        }
    }   
}

このようにOpenFeatureを用いた共通のフラグモジュールを作成できました。

サブタスク

また、メインタスクに入る前に、チームの開発に慣れる意味も兼ねて、以下のタスクに取り組みました。個人開発では体験できないようなフラグの切り替えや、バッチジョブのアドホックリリースなどを経験させていただきました。

1. Kotlin companion objectのスコープの変更

Javaのstatic修飾子がKotlinにはないため、同様の機能を使うためにはcompanion objectが使われます。これが必要以上のscopeを持っているものが複数あり、意図しないpackageからの利用のリスクがありました。これを正しいスコープに変更する作業をまず最初に行いました。この業務を通して、基本的なチームの開発フローに慣れることができました。

2. 既存APIのSpring RESTからArmeriaへの移行

LINE STOREは多言語に対応しており、ページの表示メッセージを言語ごとにファイルで保存しています。今回のタスクの移行対象であるMessageResourceControllerはユーザーが選択した言語に応じて表示する言語を切り替える役割があります。

表示言語の切り替え

既存のMessageResourceControllerはSpring MVC REST APIとして作られていますが、今回のタスクはそれをArmeria REST APIに移行することです。ArmeriaはLINEヤフーのOSSで、マイクロサービス向けフレームワークです。完全非同期・ノンブロッキングで動作するよう設計されています。この切り替えを行うためにフィーチャーフラグを利用しました。Beta、Staging、Releaseの3つの環境があり、それらで段階的にリリースしました。

移行のメトリクス

このように移行前後のメトリクスの比較をして正しく移行ができたことを確認できました。このタスクを通じて、フラグの設定の仕方や、開発環境ごとの段階的リリースの仕方を学ぶことができました。

3. 既存サービスのSpring Boot 2 → 3へのアップグレード

LINE STOREではSpring Boot 2を使用していましたが、それをSpring Boot 3にアップグレードする計画がありました。複数のサービスが移行対象でしたが、その中の1つとしてバッチジョブのアップグレードタスクを担当しました。

Spring Boot 3にアップグレードするためには、構成プロパティファイル(YAML)のマイグレーションが必要でした。(参考:https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-3.0-Migration-Guide)タスクとしては、そのファイルの変更、変更後のジョブが正常に動作するかを手動で検証するアドホックリリース、リリース後のメトリクス確認などを行いました。

アドホックリリース中にエラーが発生し、非常に焦りましたが、メンバーの皆さんの協力のおかげで無事タスクを完了できました。本番環境での実行前に問題を発見できて、本当によかったです。

感想

課題があり、それを解決するための最適なアプローチを自分で模索しながら一つの機能を作り上げる経験は、非常に貴重でした。特にインターン終盤のテックレビュー会に向けてドキュメントを作成する過程で、技術選定の理由や現状の課題、実装方針などをいかに伝わりやすく表現するかを考え抜く経験は初めてでした。一目で理解できるドキュメントを作成できたことで、より明瞭なレビューをいただき、結果としてより良い実装につながりました。

また、開発以外の面でも、多国籍なチームメンバーとの英語での業務経験や、エンジニア以外のLINE STOREメンバーとのワークショップでプロダクトについて深く考える機会など、さまざまな体験ができました。

今回のインターンで実際にチームの一員として働いたことで、プロダクトを作り上げる楽しさや、LINEヤフーの企業カルチャーなどを肌で感じられました。最後までサポートしてくださったチームの皆さん、人事の皆さん、本当にありがとうございました。

メンターからの一言

メインタスクのOpenFeature導入では、要件整理から設計・実装・ドキュメントまで一貫して推進し、既存資産との統合や運用面まで見据えた良い判断ができていました。 また、その検討内容を他チームの開発メンバーに提案するためにテックレビューを開催していただきましたが、構成・話速・Q&Aの受け答えが的確で、複雑な内容をわかりやすく伝える力が光っていたと思います。 加えて、全体的に、タスク背景の理解やTODOの切り出しが速く、トラブル時の状況整理や問いの立て方も良かったと思います。

コミュニケーション面でも、チーム内のエンジニアに限らず、プランやテストなど他チームのメンバーとも横断的かつ円滑にやり取りを進めてくださいました。

今後ますますの活躍を楽しみにしています!お疲れ様でした!