OpenFeature

OpenFeature: フィーチャーフラグの標準化とベストプラクティス

目次

  1. はじめに
  2. OpenFeatureの概要
  3. OpenFeatureのアーキテクチャ
  4. コア概念とAPI
  5. インテグレーション
  6. 実装例
  7. ユースケース
  8. ベストプラクティス
  9. まとめ

1. はじめに

1.1 フィーチャーフラグとは

フィーチャーフラグ(Feature Flag)は、本番環境でコードを実行する際に、特定の機能の有効/無効を動的に切り替えるメカニズムです。これにより以下のメリットが生まれます:

  • デプロイメントの柔軟化: コードのデプロイと機能のリリースを分離
  • リスク低減: 段階的なロールアウトやカナリアデプロイメント
  • A/Bテスト: ユーザーグループによる機能の効果検証
  • ホットフィックス: 問題が発生した場合の即時無効化

1.2 OpenFeatureが生まれた背景

フィーチャーフラグの概念自体は古くから存在しますが、各ベンダーやプロバイダー(LaunchDarkly、Split.io、CloudBees等)が異なるAPI仕様を提供していました。これにより:

  • ベンダーロックイン: 特定のプロバイダーに依存
  • 学習コスト: 異なるAPIを習得する必要
  • 相互運用性の欠如: プロバイダー間の乗り換えが困難

OpenFeatureはこうした課題を解決するため、CNCF(Cloud Native Computing Foundation)のサンドボックスプロジェクトとして2022年に設立されました。


2. OpenFeatureの概要

2.1 定義と目的

OpenFeatureは、フィーチャーフラグ管理に関するベンダー中立的な標準化仕様です。

公式定義:

OpenFeature is a vendor-agnostic, community-driven specification that defines standardized terminology, feature flag evaluation APIs, and usage guidelines for the feature flagging domain.

2.2 主要な目標

  1. 標準化(Standardization): フィーチャーフラグの共通API仕様を定義
  2. 相互運用性(Interoperability): 異なるプロバイダー間での互換性確保
  3. ベンダー中立性(Vendor Neutrality): 特定ベンダーに依存しない設計
  4. 開発者体験(DX): シンプルで直感的なAPI
  5. エコシステム構築: コミュニティドリブンな発展

2.3 現在の状況

  • CNCF所属: 2023年にIncubatingステータスに昇格
  • マルチ言語対応: Java、JavaScript/TypeScript、Python、Go、.NET、C++など
  • 複数プロバイダー対応: LaunchDarkly、Split.io、CloudBees、Unleash等
  • 成熟度: 生産環境での利用事例が増加中

3. OpenFeatureのアーキテクチャ

3.1 全体構成

┌─────────────────────────────────────────────────────────────┐
│                    Application Layer                         │
│  (開発者のアプリケーションコード)                             │
└────────────────────┬────────────────────────────────────────┘
                     │ uses
┌────────────────────▼────────────────────────────────────────┐
│              OpenFeature SDK API                             │
│  (ベンダー中立的なインターフェース)                          │
│  ┌──────────────────────────────────────────────────────┐  │
│  │ - Client (フィーチャーフラグ評価)                      │  │
│  │ - Context (ユーザーコンテキスト)                      │  │
│  │ - Hook (評価ライフサイクル拡張)                       │  │
│  │ - Event (リスナー登録)                                │  │
│  └──────────────────────────────────────────────────────┘  │
└────────────────────┬────────────────────────────────────────┘
                     │ delegates to
┌────────────────────▼────────────────────────────────────────┐
│            Provider Interface Layer                          │
│  (プロバイダー固有の実装インターフェース)                    │
│  ┌──────────────────────────────────────────────────────┐  │
│  │ - FeatureProvider                                     │  │
│  │ - ResolutionDetails                                   │  │
│  │ - EvaluationContext                                   │  │
│  └──────────────────────────────────────────────────────┘  │
└────────────────────┬────────────────────────────────────────┘
                     │ implements
┌─────────────────────────────────────────────────────────────┐
│           Concrete Provider Implementations                  │
│  ┌──────────────┐ ┌──────────────┐ ┌────────────────────┐  │
│  │LaunchDarkly  │ │ Split.io     │ │ In-Memory/File     │  │
│  │ Provider     │ │ Provider     │ │ Provider           │  │
│  └──────┬───────┘ └──────┬───────┘ └────────┬───────────┘  │
│         │                │                   │              │
└─────────┼────────────────┼───────────────────┼──────────────┘
          │                │                   │
          ▼                ▼                   ▼
    LaunchDarkly      Split.io API      ローカル設定ファイル
       API

3.2 主要コンポーネント

3.2.1 OpenFeature Client

フィーチャーフラグの評価を行うメインのインターフェース。

interface OpenFeatureClient {
  // Boolean評価
  getBooleanValue(
    flagKey: string,
    defaultValue: boolean,
    context?: EvaluationContext
  ): boolean;

  // String評価
  getStringValue(
    flagKey: string,
    defaultValue: string,
    context?: EvaluationContext
  ): string;

  // Number評価
  getNumberValue(
    flagKey: string,
    defaultValue: number,
    context?: EvaluationContext
  ): number;

  // Object評価
  getObjectValue(
    flagKey: string,
    defaultValue: object,
    context?: EvaluationContext
  ): object;

  // 詳細情報を含む評価
  getBooleanDetails(
    flagKey: string,
    defaultValue: boolean,
    context?: EvaluationContext
  ): FlagEvaluationDetails;
}

3.2.2 Evaluation Context

フィーチャーフラグの評価に必要なコンテキスト情報。ユーザー属性、セッション情報、リクエスト情報など。

interface EvaluationContext {
  targetingKey?: string;  // ユーザーID等の識別子
  custom?: {
    [key: string]: any;   // カスタム属性
  };
}

// 例
const context = {
  targetingKey: "user-123",
  custom: {
    email: "user@example.com",
    plan: "premium",
    country: "JP",
    beta_tester: true
  }
};

3.2.3 Feature Provider

フィーチャーフラグの実際の評価ロジックを実装するインターフェース。

interface FeatureProvider {
  // プロバイダーのメタデータ
  metadata: ProviderMetadata;

  // フラグの評価
  resolveBooleanValue(
    flagKey: string,
    context: EvaluationContext
  ): ResolutionDetails<boolean>;

  resolveStringValue(
    flagKey: string,
    context: EvaluationContext
  ): ResolutionDetails<string>;

  resolveNumberValue(
    flagKey: string,
    context: EvaluationContext
  ): ResolutionDetails<number>;

  resolveObjectValue(
    flagKey: string,
    context: EvaluationContext
  ): ResolutionDetails<object>;

  // ライフサイクルメソッド
  initialize?(): Promise<void>;
  shutdown?(): Promise<void>;
}

3.2.4 Resolution Details

フラグ評価の詳細結果。値だけでなく、理由やエラー情報も含む。

interface ResolutionDetails<T> {
  value: T;                    // 評価結果の値
  reason: string;              // 評価理由
  errorCode?: string;          // エラーコード
  errorMessage?: string;       // エラーメッセージ
  flagMetadata?: object;       // フラグのメタデータ
  variant?: string;            // バリアント識別子
}

// 評価理由の例
type Reason =
  | "TARGETING_MATCH"          // ターゲティングルールにマッチ
  | "TARGETING_NO_MATCH"       // ターゲティングルールにマッチしない
  | "UNSPECIFIED"              // 理由不明
  | "DISABLED"                 // フラグが無効化
  | "UNKNOWN_FLAG"             // フラグが存在しない
  | "PARSE_ERROR"              // パースエラー
  | "FLAG_NOT_FOUND"           // フラグが見つからない
  | "INTERNAL_ERROR";          // 内部エラー

3.2.5 Hook

フィーチャーフラグ評価のライフサイクルに介入するメカニズム。

interface Hook {
  // 評価前
  before?(
    context: HookContext,
    hints?: object
  ): Promise<object> | object;

  // 成功時
  after?(
    context: HookContext,
    details: FlagEvaluationDetails,
    hints?: object
  ): Promise<void> | void;

  // エラー時
  error?(
    context: HookContext,
    error: Error,
    hints?: object
  ): Promise<void> | void;

  // 最終処理
  finally?(
    context: HookContext,
    hints?: object
  ): Promise<void> | void;
}

// Hook の用途例
// - ロギングとメトリクス収集
// - コンテキスト情報の変更
// - 検証とセキュリティチェック
// - フラグ使用統計の記録

3.3 評価フロー

1. クライアント呼び出し
   ↓
2. グローバルコンテキスト + ローカルコンテキスト = 統合コンテキスト
   ↓
3. 登録済みHookのbefore()を実行
   ↓
4. プロバイダーのresolve*Value()を呼び出し
   ↓
5. 結果を取得
   ↓
6. 結果が正常な場合、after()Hookを実行
   7. 結果がエラーの場合、error()Hookを実行
   ↓
8. 最後に、finally()Hookを実行
   ↓
9. 結果をクライアントに返す

3.4 コンテキスト管理

グローバルコンテキスト

アプリケーション全体で共有されるコンテキスト。

const client = openfeature.getClient();

// グローバルコンテキストを設定
client.setContext({
  region: "JP",
  environment: "production",
  buildVersion: "2.5.1"
});

// すべての評価がこのコンテキストを使用
const flag = client.getBooleanValue("new-feature", false);

リクエストレベルコンテキスト

個別リクエストに固有のコンテキスト。グローバルコンテキストを上書き。

// API呼び出し時にコンテキストを指定
const flag = client.getBooleanValue("personalized-feature", false, {
  targetingKey: "user-456",
  custom: {
    isPremium: true,
    joinedDays: 365
  }
});

コンテキスト統合ルール

最終コンテキスト = グローバルコンテキスト + リクエストコンテキスト
(リクエストコンテキストでグローバルを上書き)

4. コア概念とAPI

4.1 フラグの種類

Boolean フラグ

最もシンプルで一般的。機能のOn/Off制御。

const isNewUIEnabled = client.getBooleanValue(
  "new-ui-enabled",
  false
);

if (isNewUIEnabled) {
  // 新しいUIを表示
  renderNewUI();
} else {
  // 既存のUIを表示
  renderOldUI();
}

String フラグ

文字列値に基づいた分岐。複数の選択肢から選択。

const theme = client.getStringValue(
  "ui-theme",
  "light",
  { targetingKey: userId }
);

// 評価結果: "light", "dark", "high-contrast" など
applyTheme(theme);

Number フラグ

数値に基づいた制御。段階的なロールアウトなど。

const apiRateLimit = client.getNumberValue(
  "api-rate-limit",
  100
);

// ユーザーグループによって異なるレート制限
limitRequests(apiRateLimit);

Object フラグ

複雑な構造を保持。複数の値を一度に取得。

const config = client.getObjectValue(
  "feature-config",
  {
    enabled: false,
    timeout: 5000,
    retryCount: 3
  }
);

const { enabled, timeout, retryCount } = config;

4.2 フラグ評価の詳細情報

// 詳細情報を含む評価
const details = client.getBooleanDetails(
  "experimental-feature",
  false,
  { targetingKey: "user-789" }
);

console.log(details);
// 出力例:
// {
//   value: true,
//   reason: "TARGETING_MATCH",
//   variant: "on",
//   flagMetadata: {
//     description: "実験的な新機能",
//     owner: "product-team"
//   }
// }

if (details.errorCode) {
  console.error(`フラグ評価エラー: ${details.errorMessage}`);
} else {
  console.log(`値: ${details.value}, 理由: ${details.reason}`);
}

4.3 イベントとリスナー

const client = openfeature.getClient();

// プロバイダーの変更をリッスン
client.on("provider-changed", () => {
  console.log("プロバイダーが変更されました");
  // UIの再描画やキャッシュの更新など
});

// プロバイダーの状態変更をリッスン
client.on("provider-ready", () => {
  console.log("プロバイダーの準備完了");
});

client.on("provider-error", (error) => {
  console.error("プロバイダーエラー:", error);
});

// リッスナーの解除
client.off("provider-changed", callback);

4.4 ターゲティングの概念

ターゲティングルールはフラグプロバイダー側で定義され、コンテキスト情報に基づいてどのユーザーグループがどの値を得るかを決定します。

ターゲティングルール例:
1. ユーザーIDが "admin-*" で始まる → "on"
2. 国が "JP" && plan が "premium" → "on"
3. beta_tester フラグが true → "on"
4. デフォルト → "off"

5. インテグレーション

5.1 主要なフィーチャーフラグプロバイダー

LaunchDarkly

最も成熟したプロバイダー。エンタープライズグレード。

特徴:

  • 高度なターゲティング機能
  • リアルタイム同期
  • 詳細な分析とレポート
  • SDKが充実

OpenFeature統合:

import { LaunchDarklyProvider } from '@launchdarkly/openfeature-server-sdk';

const provider = new LaunchDarklyProvider(
  'sdk-key-here',
  { maxCacheSize: 1000 }
);

openfeature.setProvider(provider);

Split.io

インクリメンタルなA/Bテストと機能フラグ管理に特化。

特徴:

  • シンプルで直感的なUI
  • 統計分析に強い
  • マルチプロダクト対応
  • ユーザーフレンドリー

OpenFeature統合:

import { SplitProvider } from '@splitsoftware/openfeature-js-sdk';

const provider = new SplitProvider({
  authorizationKey: 'your-split-key'
});

openfeature.setProvider(provider);

CloudBees Feature Management

CloudBeesエコシステムに統合。CI/CDパイプラインとの連携が強い。

Unleash

オープンソースのフィーチャーフラグ管理プラットフォーム。

特徴:

  • オープンソース
  • セルフホスト可能
  • 無料・低コスト
  • プライバシー重視

OpenFeature統合:

import { UnleashProvider } from '@unleash/openfeature-provider-sdk';

const provider = new UnleashProvider({
  url: 'https://unleash.example.com',
  appName: 'my-app',
  instanceId: 'production-1'
});

openfeature.setProvider(provider);

Flags

シンプルなセルフホスト型ソリューション。

In-Memory Provider

テストやプロトタイピング用。ローカルメモリに設定を保持。

import { InMemoryProvider } from '@openfeature/js-sdk';

const provider = new InMemoryProvider({
  new_feature: {
    state: 'ENABLED',
    variants: {
      on: true,
      off: false
    },
    defaultVariant: 'off'
  }
});

openfeature.setProvider(provider);

5.2 プロバイダーの選定基準

基準LaunchDarklySplit.ioUnleashIn-Memory
機能の充実度⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐
コスト低/無料無料
セルフホスト
A/Bテスト⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐-
リアルタイム⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐
学習曲線最低

6. 実装例

6.1 JavaScript/TypeScript での実装

基本的なセットアップ

import { OpenFeature } from '@openfeature/js-sdk';
import { LaunchDarklyProvider } from '@launchdarkly/openfeature-server-sdk';

// プロバイダーの初期化
const provider = new LaunchDarklyProvider('your-sdk-key', {
  baseUrl: 'https://app.launchdarkly.com',
  streamUrl: 'https://stream.launchdarkly.com',
  maxCacheSize: 1000
});

// OpenFeatureにプロバイダーを登録
OpenFeature.setProvider(provider);

// クライアントの取得
const client = OpenFeature.getClient();

// コンテキスト(グローバル)の設定
client.setContext({
  organization: 'acme-corp',
  environment: 'production'
});

フラグ評価の実装

// シンプルなフラグ評価
async function showFeature(userId: string) {
  const isEnabled = client.getBooleanValue(
    'new-dashboard-enabled',
    false
  );

  if (isEnabled) {
    console.log('新ダッシュボードを表示');
    renderNewDashboard();
  } else {
    console.log('既存ダッシュボードを表示');
    renderOldDashboard();
  }
}

// ユーザーコンテキスト付きの評価
async function personalizedExperience(userId: string) {
  const context = {
    targetingKey: userId,
    custom: {
      email: 'user@example.com',
      isPremium: true,
      signupDate: '2022-01-15'
    }
  };

  const experience = client.getStringValue(
    'user-experience-variant',
    'standard',
    context
  );

  // 'standard', 'premium', 'enterprise' のいずれかが返される
  loadUserExperience(experience);
}

// 詳細情報を含む評価
async function evaluateWithDetails(userId: string) {
  const details = client.getBooleanDetails(
    'experimental-feature',
    false,
    { targetingKey: userId }
  );

  console.log(`評価値: ${details.value}`);
  console.log(`理由: ${details.reason}`);
  console.log(`バリアント: ${details.variant}`);

  // エラーハンドリング
  if (details.reason === 'UNKNOWN_FLAG') {
    console.warn('フラグが見つかりません');
  } else if (details.errorCode) {
    console.error(`エラー: ${details.errorMessage}`);
  }

  return details.value;
}

Hookの実装

// ロギング用Hook
const loggingHook = {
  before: (context) => {
    console.log('[Hook Before] Context:', context);
    return {};
  },

  after: (context, details) => {
    console.log('[Hook After] Result:', {
      flagKey: context.flagKey,
      value: details.value,
      reason: details.reason
    });
  },

  error: (context, error) => {
    console.error('[Hook Error]', {
      flagKey: context.flagKey,
      error: error.message
    });
  },

  finally: (context) => {
    console.log('[Hook Finally] Cleanup for:', context.flagKey);
  }
};

// メトリクス収集用Hook
const metricsHook = {
  after: (context, details) => {
    // メトリクスをDatadogに送信
    sendMetric({
      name: 'flag_evaluation',
      flagKey: context.flagKey,
      value: details.value ? 1 : 0,
      reason: details.reason,
      timestamp: Date.now()
    });
  }
};

// Hookを登録
client.addHook(loggingHook);
client.addHook(metricsHook);

イベントリスナーの実装

// プロバイダー変更時の処理
client.on('provider-changed', () => {
  console.log('プロバイダーが変更されました');
  // キャッシュのクリア
  clearFlagCache();
  // UIの再描画
  refreshUI();
});

// プロバイダーエラーハンドリング
client.on('provider-error', (error) => {
  console.error('プロバイダーエラー:', error);
  // フォールバック処理
  switchToInMemoryProvider();
});

// 準備完了イベント
client.on('provider-ready', () => {
  console.log('フィーチャーフラグシステムの準備完了');
  startApplication();
});

6.2 Java での実装

import com.launchdarkly.sdk.ContextBuilder;
import com.launchdarkly.sdk.LDContext;
import dev.openfeature.sdk.Client;
import dev.openfeature.sdk.OpenFeature;
import dev.openfeature.sdk.android.LDProvider;

public class FeatureFlagManager {
  private final Client client;

  public FeatureFlagManager() {
    // LaunchDarklyプロバイダーの初期化
    LDProvider provider = new LDProvider(
        "sdk-key-here",
        new LDConfig.Builder("sdk-key-here")
            .offline(false)
            .build()
    );

    OpenFeature.setProvider(provider);
    this.client = OpenFeature.getClient();

    // グローバルコンテキストの設定
    LDContext globalContext = ContextBuilder.multi(
        ContextBuilder.createSingleContext("user", "global-id")
            .set("environment", "production")
            .build(),
        ContextBuilder.createSingleContext("organization", "org-123")
            .set("region", "APAC")
            .build()
    ).build();

    client.setEvaluationContext(globalContext);
  }

  // フラグ評価
  public void evaluateFeatures(String userId) {
    // ユーザーコンテキスト
    LDContext userContext = ContextBuilder.createSingleContext("user", userId)
        .set("email", "user@example.com")
        .set("isPremium", true)
        .build();

    // Boolean評価
    boolean isNewFeatureEnabled = client.getBooleanValue(
        "new-feature-flag",
        false,
        userContext
    );

    if (isNewFeatureEnabled) {
      enableNewFeature();
    }

    // 詳細情報付き評価
    FlagEvaluationDetails details = client.getBooleanDetails(
        "experiment-flag",
        false,
        userContext
    );

    System.out.println("Value: " + details.getValue());
    System.out.println("Reason: " + details.getReason());
    System.out.println("Variant: " + details.getVariant());
  }

  // リスナーの登録
  public void registerListeners() {
    client.on("provider-ready", event -> {
      System.out.println("フィーチャーフラグシステム準備完了");
    });

    client.on("provider-error", event -> {
      System.err.println("プロバイダーエラー: " + event.getError());
    });
  }
}

6.3 Python での実装

from openfeature import OpenFeature, EvaluationContext
from openfeature.provider import UnleashProvider

# プロバイダーの初期化
provider = UnleashProvider(
    url="https://unleash.example.com",
    app_name="my-python-app",
    instance_id="production-1"
)

# OpenFeatureにプロバイダーを登録
OpenFeature.set_provider(provider)
client = OpenFeature.get_client()

# グローバルコンテキストの設定
client.set_context({
    "organization": "acme-corp",
    "environment": "production"
})

def evaluate_features_for_user(user_id: str):
    # ユーザーコンテキスト
    context = EvaluationContext(
        targeting_key=user_id,
        attributes={
            "email": "user@example.com",
            "plan": "premium",
            "country": "JP"
        }
    )

    # Boolean評価
    is_enabled = client.get_boolean_value(
        "new-dashboard",
        False,
        context
    )

    # Object評価
    config = client.get_object_value(
        "feature-config",
        {
            "timeout": 5000,
            "retryCount": 3
        },
        context
    )

    # 詳細情報付き評価
    details = client.get_boolean_details(
        "experiment-flag",
        False,
        context
    )

    print(f"Value: {details.value}")
    print(f"Reason: {details.reason}")
    print(f"Variant: {details.variant}")

    return is_enabled

# Hook の登録
class LoggingHook:
    async def before(self, context, hints):
        print(f"Evaluating flag: {context.flag_key}")
        return {}

    async def after(self, context, details, hints):
        print(f"Result: {details.value}")

    async def error(self, context, error, hints):
        print(f"Error: {error}")

client.add_hook(LoggingHook())

6.4 Go での実装

package main

import (
	"context"
	"fmt"
	"log"

	"github.com/open-feature/go-sdk/pkg/client"
	"github.com/open-feature/go-sdk/pkg/openfeature"
	ldflag "github.com/launchdarkly/go-server-sdk/v6"
	ldProvider "github.com/launchdarkly/go-openfeature-server-hooks/pkg"
)

func main() {
	// LaunchDarklyプロバイダーの初期化
	ldClient, err := ldflag.MakeClient("sdk-key-here", 5*time.Second)
	if err != nil {
		log.Fatalf("Failed to create LaunchDarkly client: %v", err)
	}

	provider := ldProvider.NewProvider(ldClient)
	openfeature.SetProvider(provider)

	// クライアント取得
	client := openfeature.GetClient()
	ctx := context.Background()

	// グローバルコンテキスト設定
	client.SetContext(ctx, &openfeature.EvaluationContext{
		Attributes: map[string]interface{}{
			"organization": "acme-corp",
			"environment":  "production",
		},
	})

	// フラグ評価
	evaluateFeatures(ctx, client)
}

func evaluateFeatures(ctx context.Context, client client.Client) {
	// Boolean評価
	isEnabled, details, err := client.BooleanValue(
		ctx,
		"new-feature",
		false,
		&openfeature.EvaluationContext{
			TargetingKey: "user-123",
			Attributes: map[string]interface{}{
				"email":     "user@example.com",
				"isPremium": true,
			},
		},
	)

	if err != nil {
		fmt.Printf("Error evaluating flag: %v\n", err)
		return
	}

	fmt.Printf("Is Enabled: %v\n", isEnabled)
	fmt.Printf("Reason: %v\n", details.Reason)
	fmt.Printf("Variant: %v\n", details.Variant)

	// String評価
	theme, _, err := client.StringValue(
		ctx,
		"ui-theme",
		"light",
		&openfeature.EvaluationContext{
			TargetingKey: "user-123",
		},
	)

	if err != nil {
		fmt.Printf("Error evaluating theme: %v\n", err)
		return
	}

	fmt.Printf("Theme: %v\n", theme)
}

7. ユースケース

7.1 段階的なロールアウト (Gradual Rollout)

新機能を段階的に本番環境にリリース。

// 初期は0%のユーザーが有効
// day 1: 5% のユーザーに有効化
// day 3: 25% のユーザーに有効化
// day 5: 100% のユーザーに有効化

const isNewPaymentFlow = client.getBooleanValue(
  'new-payment-flow',
  false,
  { targetingKey: userId }
);

// LaunchDarklyの設定例:
// - 初期状態: 0% rollout
// - 属性ベースのターゲティング: なし
// - ルール: userId の hash に基づいた段階的ロールアウト

7.2 カナリアデプロイメント

新しいバージョンを一部のトラフィックにのみ送信。

const apiVersion = client.getStringValue(
  'api-version',
  'v1',
  { 
    targetingKey: userId,
    custom: { region: userRegion }
  }
);

// v1, v2, canary のいずれかを返す
const endpoint = getApiEndpoint(apiVersion);
const response = await fetch(endpoint, options);

7.3 ユーザー セグメンテーション

異なるユーザーグループに異なる機能を提供。

// プレミアムユーザーには高度なレポート機能を有効化
const advancedReports = client.getBooleanValue(
  'advanced-reports',
  false,
  {
    targetingKey: userId,
    custom: {
      plan: 'premium',
      monthlySpend: 1000
    }
  }
);

// ベータテスターにのみ新機能を提供
const betaFeature = client.getBooleanValue(
  'beta-new-ui',
  false,
  {
    targetingKey: userId,
    custom: {
      isBetaTester: true
    }
  }
);

7.4 A/Bテスト

ユーザーを異なるバリアント間で分割し、効果を測定。

const variant = client.getStringValue(
  'checkout-variant',
  'control',
  { targetingKey: userId }
);

// 結果: 'control', 'variant-a', 'variant-b'

switch (variant) {
  case 'control':
    renderCheckoutV1();
    break;
  case 'variant-a':
    renderCheckoutV2WithNewButton();
    break;
  case 'variant-b':
    renderCheckoutV2WithSteps();
    break;
}

// メトリクス送信
trackEvent('checkout-variant', {
  variant: variant,
  userId: userId,
  timestamp: Date.now()
});

7.5 インスタント リリース & ロールバック

問題検出時に即座に機能を無効化。

// 本番環境で問題検出
// → LaunchDarkly UIから即座に "payment-processor-v2" を OFF
// → すべてのユーザーが既存システムに自動フォールバック

const paymentVersion = client.getStringValue(
  'payment-processor',
  'v1',
  { targetingKey: userId }
);

if (paymentVersion === 'v2') {
  useNewPaymentProcessor();
} else {
  useOldPaymentProcessor(); // フォールバック
}

7.6 地域別機能制御

地域によって異なる機能を提供。

const isFeatureAvailable = client.getBooleanValue(
  'feature-by-region',
  false,
  {
    targetingKey: userId,
    custom: {
      country: userCountry,  // 'JP', 'US', 'EU' など
      region: userRegion
    }
  }
);

// LaunchDarklyの設定:
// EU圏: GDPR対応機能を有効化
// 日本: 日本語ローカライズ版を有効化
// その他: ベータ版を無効化

7.7 パフォーマンス テスト時の制御

負荷テスト時に特定の機能を制御。

const isLoadTest = client.getBooleanValue(
  'disable-analytics-during-load-test',
  false,
  {
    custom: {
      isLoadTest: true,
      loadTestId: 'perf-test-2026-04-07'
    }
  }
);

if (!isLoadTest) {
  sendAnalytics(); // 本番環境では実行、テスト時は スキップ
}

8. ベストプラクティス

8.1 フラグ命名規約

[領域]-[機能名]-[目的]

例:
- payment-processor-v2-rollout
- ui-new-dashboard-ab-test
- api-rate-limiting-gradual-rollout
- analytics-tracking-beta-test
- performance-cache-optimization-internal

規約のポイント:

  • 小文字とハイフンを使用
  • 具体的でわかりやすい名前
  • 目的や状態を含める

8.2 デフォルト値の設定

// 良い例: 明示的なデフォルト値
const isEnabled = client.getBooleanValue(
  'new-feature',
  false  // 明示的な有効状態は false
);

// 危険な例: 存在しない場合の動作が曖昧
const isEnabled = client.getBooleanValue('new-feature', undefined);

8.3 エラーハンドリング

const details = client.getBooleanDetails(
  'important-feature',
  false
);

if (details.errorCode) {
  // エラーハンドリング
  console.error(`Flag evaluation failed: ${details.errorMessage}`);

  // ロギング
  logError({
    flagKey: 'important-feature',
    errorCode: details.errorCode,
    errorMessage: details.errorMessage
  });

  // フォールバック
  return false; // デフォルト値を使用
}

return details.value;

8.4 コンテキスト設計

// 良い例: 完全で一貫性のあるコンテキスト
const context = {
  targetingKey: userId,
  custom: {
    email: userEmail,
    plan: userPlan,
    signupDate: userSignupDate,
    isActive: userIsActive,
    lastLoginDate: userLastLogin,
    country: userCountry,
    // PII以外の有用な情報を含める
  }
};

// 避けるべき例: PII情報を含める
const context = {
  targetingKey: userId,
  custom: {
    password: userPassword,       // ❌ 絶対に含めない
    creditCard: userCreditCard,   // ❌ 絶対に含めない
    ssn: userSSN,                 // ❌ 絶対に含めない
    // ...
  }
};

8.5 パフォーマンス最適化

キャッシング戦略

// フラグ評価の結果をキャッシュ
const flagCache = new Map();

function getCachedFlag(flagKey: string, context: any) {
  const cacheKey = `${flagKey}:${JSON.stringify(context)}`;

  if (flagCache.has(cacheKey)) {
    return flagCache.get(cacheKey);
  }

  const result = client.getBooleanValue(flagKey, false, context);
  flagCache.set(cacheKey, result);

  // 5分後にキャッシュ削除
  setTimeout(() => flagCache.delete(cacheKey), 5 * 60 * 1000);

  return result;
}

バッチ評価

// 複数フラグを一度に評価
async function evaluateAllFlags(userId: string) {
  const context = { targetingKey: userId };

  // 並列実行
  const [flag1, flag2, flag3] = await Promise.all([
    client.getBooleanValue('feature-1', false, context),
    client.getBooleanValue('feature-2', false, context),
    client.getBooleanValue('feature-3', false, context),
  ]);

  return { flag1, flag2, flag3 };
}

8.6 テスティング

単体テスト

import { InMemoryProvider } from '@openfeature/js-sdk';

describe('Feature Flag Logic', () => {
  beforeEach(() => {
    const provider = new InMemoryProvider({
      'test-flag': {
        state: 'ENABLED',
        variants: {
          on: true,
          off: false,
        },
        defaultVariant: 'off',
      }
    });

    OpenFeature.setProvider(provider);
  });

  it('should enable feature when flag is on', () => {
    const client = OpenFeature.getClient();
    const isEnabled = client.getBooleanValue('test-flag', false);

    expect(isEnabled).toBe(true);
  });
});

統合テスト

describe('LaunchDarkly Integration', () => {
  it('should evaluate flags from LaunchDarkly', async () => {
    const provider = new LaunchDarklyProvider('test-key');
    await provider.initialize();

    OpenFeature.setProvider(provider);

    const client = OpenFeature.getClient();
    const result = await client.getBooleanValue(
      'integration-test-flag',
      false
    );

    expect(result).toBeDefined();
  });
});

8.7 セキュリティ考慮事項

// 1. コンテキスト情報のサニタイズ
function sanitizeContext(context: any) {
  const sanitized = { ...context };

  // 個人情報を削除
  delete sanitized.password;
  delete sanitized.creditCard;
  delete sanitized.apiKey;

  return sanitized;
}

// 2. SDKキーの安全な保管
// 環境変数から取得
const sdkKey = process.env.LAUNCHDARKLY_SDK_KEY;

// 3. プロバイダーの認証
const provider = new LaunchDarklyProvider(sdkKey, {
  tlsSkipVerify: false,  // 本番環境では常に true
  baseUrl: 'https://',   // HTTP の使用は避ける
});

// 4. ログの適切な処理
// 機密情報をログに出力しない
console.log('Flag evaluation:', {
  flagKey: flagKey,
  result: result,
  // ❌ context は含めない (PII を含む可能性)
});

8.8 監視とロギング

const monitoringHook = {
  before: (context) => {
    recordMetric('flag.evaluation.started', {
      flagKey: context.flagKey,
      timestamp: Date.now()
    });
    return {};
  },

  after: (context, details) => {
    recordMetric('flag.evaluation.completed', {
      flagKey: context.flagKey,
      success: true,
      value: details.value,
      reason: details.reason,
      duration: Date.now() - startTime
    });
  },

  error: (context, error) => {
    recordMetric('flag.evaluation.error', {
      flagKey: context.flagKey,
      errorCode: error.code,
      errorMessage: error.message
    });
  }
};

client.addHook(monitoringHook);

9. トラブルシューティング

9.1 よくある問題と解決方法

問題1: フラグが常に false を返す

// 原因: デフォルト値が使用されている
// 解決方法: プロバイダー接続を確認

const provider = OpenFeature.getProvider();
console.log('Provider Status:', provider);

// プロバイダーが初期化されているか確認
if (!provider.initialized) {
  await provider.initialize();
}

問題2: コンテキスト情報が反映されない

// 原因: コンテキスト形式が不正
// 解決方法: コンテキスト構造を確認

// 正しい形式
const context = {
  targetingKey: 'user-123',
  custom: {
    plan: 'premium'
  }
};

// 誤った形式
const context = {
  userId: 'user-123',      // ❌ targetingKey ではなく userId
  attributes: { plan: 'premium' }  // ❌ custom ではなく attributes
};

問題3: パフォーマンス低下

// 原因: 過度なネットワークリクエスト
// 解決方法: キャッシングとバッチ処理を実装

// キャッシング有効化
const provider = new LaunchDarklyProvider('sdk-key', {
  maxCacheSize: 1000,      // キャッシュサイズを設定
  cacheTTL: 300000,         // 5分の TTL
});

// バッチ処理
async function evaluateMultipleFlags(userId: string) {
  const flags = await Promise.all([
    client.getBooleanValue('flag1', false),
    client.getBooleanValue('flag2', false),
  ]);

  return flags;
}

9.2 デバッグ技法

// デバッグモード有効化
const debugHook = {
  before: (context) => {
    console.debug('[OpenFeature Debug]', {
      flagKey: context.flagKey,
      context: context,
      timestamp: new Date().toISOString()
    });
    return {};
  },

  after: (context, details) => {
    console.debug('[OpenFeature Result]', {
      flagKey: context.flagKey,
      value: details.value,
      reason: details.reason,
      variant: details.variant,
      metadata: details.flagMetadata
    });
  }
};

if (process.env.DEBUG_FLAGS === 'true') {
  client.addHook(debugHook);
}

10. まとめ

10.1 OpenFeatureの価値

  1. ベンダー中立性: 特定ベンダーへの依存を軽減
  2. 標準化: 統一的なAPI により開発効率が向上
  3. 相互運用性: プロバイダー間の切り替えが容易
  4. エコシステム: 豊富なツールとプロバイダーを活用可能
  5. 学習コスト低下: 一度の学習で複数ベンダーに対応

10.2 採用の段階

Phase 1: 導入検討 (1-2週間)

  • OpenFeature仕様の学習
  • 利用可能なプロバイダーの調査
  • 要件に合致するプロバイダーの選定

Phase 2: パイロット導入 (2-4週間)

  • テスト環境でのセットアップ
  • 基本的なフラグ評価の実装
  • Hook とイベントの実装

Phase 3: 本番環境への展開 (4-8週間)

  • 段階的なロールアウト
  • モニタリングとロギングの整備
  • パフォーマンスチューニング

Phase 4: 運用最適化 (継続的)

  • フラグ使用量の分析
  • 不要なフラグの削除
  • ベストプラクティスの定着

10.3 今後の展望

  • より多くのプロバイダー統合: OpenFeatureスペックへの準拠が標準に
  • エッジコンピューティング対応: CDNやエッジノードでのフラグ評価
  • AI/ML連携: 機械学習を用いた動的ターゲティング
  • 業界標準化の進展: CNCF による更なる成熟化

10.4 選定ガイド

LaunchDarkly を選ぶべき場合:

  • エンタープライズグレードの機能が必要
  • リアルタイム同期が重要
  • 詳細な分析が必要
  • 予算が潤沢にある

Split.io を選ぶべき場合:

  • A/Bテストに特化したい
  • シンプルで使いやすいUIが必要
  • 統計分析機能が重要

Unleash を選ぶべき場合:

  • セルフホスト環境を構築したい
  • オープンソースソリューションを好む
  • コスト削減が優先
  • プライバシー重視

In-Memory Provider を選ぶべき場合:

  • テストやプロトタイピング段階
  • シンプルな機能で十分
  • 外部依存を最小化したい

11. リソース

公式リソース

参考資料

コミュニティ

  • GitHub Discussions: OpenFeature公式リポジトリ
  • CNCF Slack
  • StackOverflow (openfeature タグ)

作成日: 2026年4月7日 バージョン: 1.0 言語: 日本語