OpenFeature
OpenFeature: フィーチャーフラグの標準化とベストプラクティス
目次
- はじめに
- OpenFeatureの概要
- OpenFeatureのアーキテクチャ
- コア概念とAPI
- インテグレーション
- 実装例
- ユースケース
- ベストプラクティス
- まとめ
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 主要な目標
- 標準化(Standardization): フィーチャーフラグの共通API仕様を定義
- 相互運用性(Interoperability): 異なるプロバイダー間での互換性確保
- ベンダー中立性(Vendor Neutrality): 特定ベンダーに依存しない設計
- 開発者体験(DX): シンプルで直感的なAPI
- エコシステム構築: コミュニティドリブンな発展
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 プロバイダーの選定基準
| 基準 | LaunchDarkly | Split.io | Unleash | In-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の価値
- ベンダー中立性: 特定ベンダーへの依存を軽減
- 標準化: 統一的なAPI により開発効率が向上
- 相互運用性: プロバイダー間の切り替えが容易
- エコシステム: 豊富なツールとプロバイダーを活用可能
- 学習コスト低下: 一度の学習で複数ベンダーに対応
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. リソース
公式リソース
- OpenFeature 公式サイト: https://openfeature.dev
- GitHub リポジトリ: https://github.com/open-feature
- 仕様ドキュメント: https://openfeature.dev/docs/specification
- SDK ドキュメント: 各言語の SDK GitHub リポジトリ
参考資料
- CNCF プロジェクト情報: https://www.cncf.io
- 各プロバイダーのドキュメント
- LaunchDarkly: https://launchdarkly.com/docs/
- Split.io: https://help.split.io/
- Unleash: https://docs.getunleash.io/
コミュニティ
- GitHub Discussions: OpenFeature公式リポジトリ
- CNCF Slack
- StackOverflow (openfeature タグ)
作成日: 2026年4月7日 バージョン: 1.0 言語: 日本語