☺️

OpenFeatureとFlagsmithをNext.js 14で動かしてみた

2024/06/30に公開

はじめに

初めまして!
この記事は、「OpenFeature SDK v1がリリースされたし、GW中にいっちょNext.jsとFlagsmithを組み合わせて動かしてみるか!」と思い立って書いた記事なります!
GWから2月弱すぎていることには目を瞑って下さい😊

1.Feature Flagって何?

凄くシンプルなものだと、以下のようにif文を使った条件分岐です!
この例だと、featureFlagの変数が、trueだと新機能が有効されます。

export const Page = () => {
  if (featureFlag) {
    return <NewFeature />
  } else {
    return <CurrentFeature />
  }
}

2.Feature Flagの何が嬉しい?

弊社でも、上記のような簡単なFeature Flagを用いて新機能開発を行なっていますが、以下の効果を実感できています!

  • コンフリクトの影響を小さくできる
  • 問題があった場合、旧機能に簡単に差し替えれる
  • 開発中の新機能であっても、mainにマージして問題ない
  • 使い方によっては、一部の企業やユーザなどに先行して機能を提供できる

3.Feature Flagの値ってどこから取得するの??

色々やり方はありそうですが、業務上で使われるのは主に以下の2つだと思います。
ただ、継続的な開発を考えると、実質的には2番一択かと・・・

  1. 環境変数から取得する方法
    • 低コストで簡単に始められるが、ON/OFFが面倒 & 多数のFlag管理には適さない
  2. Feature Flagを管理するシステムから取得する方法
    • 導入が手間だが、運用が軌道に乗ると非常に便利。環境変数と比べるとコストはかかる。

4.Feature Flagを管理するシステムって自作するの??大変じゃない?

安心して下さい!昔は社内で自作していましたが、昨今はSaaSやOSSを利用するのが一般的です!
有名どころだけでも、以下のサービスがあります!

5.OpenFeatureって?

Feature Flagを提供するSaaSは非常にたくさん数があります。
ですが、その仕様はサービスによって全く異なります。
そうなると、Feature Flagという同じ仕組みを使いたいだけなのにベンダーロックインされてしまう問題が発生します。
それを解消するために、作られたのがOpenFeatureという規格です。
下記の図で、Feature Flagging Service に当たるのが、DevCycle や Flagsmithです。
このままでは、異なるFeature Flagのサービスに乗り換えるたびに、client部分を全て修正しなければなりません。

そこに、OpenFeatureが登場することで、以下のようになります。
OpenFeature SKDに各サービスのプロバイダを設定してやるだけです。
そうすれば、アプリケーションからは、OpenFeature SDKと呼ばれる標準化された規格のAPIをコールだけで良くなります。
異なるSaaSに乗り換えたい場合は、プロバイダの設定を変えてやるだけで済むので、アプリケーション側のロジックは一切変更不要です!嬉しい!

6.実際に使ってみた

というわけで、長々と説明しましたが、実際にNext.jsで使ってみて使用感を確認してみましょう。
以下に、完成したリポジトリを用意していますので、今後はこれをベースに説明していきます。
よかったら手元にcloneして、一緒に確認してみて下さい!
https://github.com/KaiTakabe0301/next-flagsmith-openfeature-demo

6-1.開発環境

この記事で利用した各パッケージは、以下の通りです。

@openfeature/flagsmith-client-provider@0.1.2
@openfeature/react-sdk@0.3.4
flagsmith-nodejs@3.3.0
flagsmith@4.0.0
next@14.2.3
react-dom@18.3.1
react@18.3.1
typescript@5.4.5

6-2.リポジトリの説明

このリポジトリには、2本のブランチが存在します。
各ブランチは、以下のような構成になっており、6-3節で利用するのがmainブランチ、6-4節で利用するのがwith-open-feature-providerブランチになります。

  • mainブランチ ... Next.js + Flagsmithのみの構成
  • with-open-feature-providerブランチ ... Next.js + OpenFeature + Flagsmithの構成

また、appディレクトリに、それぞれ2つのパスを用意しています。

  • app/client-side-sdk ... Client SideのSDKを利用した方法
  • app/server-side-sdk ... Server SideのSDKを利用した方法

6-3.まず、Flagsmithだけ使ってみる

Flagsmithのみの使用感を確認してみましょう!
mainブランチに切り替えてもらうことで、これらを確認することができます!

6-3-1. Providerの作成

まず、Next.js 14でFlagsmithを利用するには、以下のようなプロバイダを作る必要があります。
※mainブランチでは、既に実装済みです

src/app/provider.tsx
"use client";

import { createFlagsmithInstance } from "flagsmith/isomorphic";
import { FlagsmithProvider } from "flagsmith/react";
import { IState } from "flagsmith/types";
import { ReactElement, useRef } from "react";

export default function Provider({
  children,
  flagsmithState,
}: {
  children: React.ReactNode;
  flagsmithState?: IState<string, string>;
}) {
  const flagsmithRef = useRef(createFlagsmithInstance());
  return (
    <FlagsmithProvider
      flagsmith={flagsmithRef.current}
      serverState={flagsmithState}
    >
      {children as ReactElement}
    </FlagsmithProvider>
  );
}

そして、作成したプロバイダを、src/app/layout.tsx に設定します。

src/app/layout.tsx
import { createFlagsmithInstance } from "flagsmith/isomorphic";
import type { Metadata } from "next";
import { Inter } from "next/font/google";
import { ReactElement } from "react";
import "./globals.css";
import Provider from "./provider";

const inter = Inter({ subsets: ["latin"] });

export const metadata: Metadata = {
  title: "Create Next App",
  description: "Generated by create next app",
};

export default async function RootLayout({
  children,
}: Readonly<{
  children: React.ReactNode;
}>) {
  const flagsmithSSR = createFlagsmithInstance();
  const flagsmithState = await flagsmithSSR
    .init({
      // fetches flags on the server
      environmentID: "EuAfFiumzd5hkiNLU49zTt", // substitute your env ID
      api: "http://localhost:8000/api/v1/",
    })
    .then(() => {
      return flagsmithSSR.getState();
    });
  return (
    <html lang="en">
      <body className={inter.className}>
        <Provider flagsmithState={flagsmithState}>
          {children as ReactElement}
        </Provider>
      </body>
    </html>
  );
}

6-3-2.Flagsmithを立ち上げる

プロジェクトのルートディレクトリで、以下のコマンドを入力してFlagsmithを立ち上げて下さい。

docker-compose up

無事に立ち上がると、以下のメッセージが表示されます。

Superuser "admin@example.com" created successfully.
flagsmith-1            | Please go to the following page and choose a password: http://localhost:8000/password-reset/confirm/MQ/c6vb9n-2114a64764037b9655d996ae39eb1739

この表示されているURLにアクセスすると、以下のようなパスワード再設定画面が表示されます。

ここで、任意のパスワードを設定すると、ログイン画面にリダイレクトされるので、先ほど設定したパスワードを入力してログインして下さい。

ログインが完了したら、プロジェクト選択の画面が表示されます。
ここでは、簡単に進めたいのでDefault Projectを選択します。

次に環境を作成するように指示されるので、開発用の環境「Develop」を作成します。

無事に環境が作成されると、以下のような画面が表示されると思います。

6-3-3.Feature Flagを作成する

画面右上にある「Create Feature」をクリックすると、以下のようなFeature Flagを作成するパネルが表示されます。
ここで、show_demo_buttonという名前でFeature Flagを作成して下さい。

無事に作成されると、ダッシュボード上に作成したFeature Flagが表示されます。

6-3-4.Client-side Environment Keyの設定する

このままでは、作成したFeature Flagを利用できません。
次に、ローカルで立ち上がっているFlagsmithのServer-side Environment Keysを、ソースコード上に設定します。

画面左のメニューから、「SDK Keys」をクリックすると、下図のようにClient-side Environment Keyが表示されます。

「Create Server-side Environment Key」をクリックして、Server-side Environment Keysを作成します。ここでは名前を practice としました。

あとは、この値をコピーして、src/app/layout.tsxの24行目に記載されているenvironmentIDに設定して下さい。
この設定は、Flagsmithが提供しているカスタムフックuseFlagsを利用する際に必要になります。

src/app/layout.tsx
    .init({
      // fetches flags on the server
      environmentID: "XRAt6CNJZdUvsMpyPeqejr", // substitute your env ID
      api: "http://localhost:8000/api/v1/",
    })

6-3-5.Server-side Environment KeysのIDを設定する

次に、src/app/server-side-sdk/page.tsxの6行目に記載されているenvironmentKeyに、Server-side Environment Keysを設定しましょう。

まず、

最後に、作成した値をコピーしたら完了です!

こちらの設定は、useFlagsを使わずに、サーバ側でのみFeature Flagの評価を行いたい場合に利用します。

src/app/server-side-sdk/page.tsx
const flagsmith = new Flagsmith({
  environmentKey: "ser.SehjnZW6CM7WTQFq85TeDe",
  apiUrl: "http://localhost:8000/api/v1/",
});

これで、ようやくFeature FlagをNext.jsで利用する準備が整いました!

6-3-6.show_demo_buttonをON/OFFしてみる

それでは、早速show_demo_buttonの挙動を確認してみましょう!
以下のコマンドで、Next.jsを立ち上げて下さい。

npm install
npm run dev

立ち上がったら、以下のURLにアクセスしてみて下さい。

  • http://localhost:3000/client-side-sdk ... Client Side SDKで構成されているページ
  • http://localhost:3000/sever-side-sdk ... Server Side SDKで構成されているページ

以下のように、テキストだけが表示されていると思います。

次に、Flagsmithの画面で、show_demo_buttonの値をtrueに変更してみましょう。

この状態で、ページをリフレッシュすると、以下の画像のようにボタンが表示されるようになります!

これで、Next.js上でFlagsmithを利用できることが確認できました!

6-4. OpenFeatureのプロバイダに、Flagsmithを設定してみる

次に、OpenFeatureを利用して、先ほどと同様にFlagsmithを利用してみましょう!
ブランチを、with-open-feature-providerに切り替えて下さい。
その後、以下のコマンドを実行してNext.jsを立ち上げ直して下さい。

6-4-1. プロバイダの修正

先ほどmainブランチで利用したプロバイダは、Flagsmith用に作成したものなので、これをOpenFeatureを利用する形式に修正する必要があります。
※with-open-feature-providerブランチでは、既に実装済みです

src/app/provider.tsx
"use client";

import { ReactElement } from "react";
import { OpenFeature, OpenFeatureProvider } from "@openfeature/react-sdk";
import { FlagsmithClientProvider } from "@openfeature/flagsmith-client-provider";
import { IState } from "flagsmith/types";

export default function Provider({
  children,
  serverState,
}: {
  children: React.ReactNode;
  serverState: IState<string, string>;
}) {
  const flagsmithClientProvider = new FlagsmithClientProvider({
    environmentID: "XRAt6CNJZdUvsMpyPeqejr",
    api: "http://localhost:8000/api/v1/",
    state: serverState,
  });
  OpenFeature.setProvider(flagsmithClientProvider); // Attach the provider to OpenFeature
  return <OpenFeatureProvider>{children as ReactElement}</OpenFeatureProvider>;
}

上記のプロバイダをsrc/app/layout.tsx に設定して下さい。
※ propsの名前が変わっているだけです

src/app/layout.tsx
import { createFlagsmithInstance } from "flagsmith/isomorphic";
import type { Metadata } from "next";
import { Inter } from "next/font/google";
import { ReactElement } from "react";
import "./globals.css";
import Provider from "./provider";

const inter = Inter({ subsets: ["latin"] });

export const metadata: Metadata = {
  title: "Create Next App",
  description: "Generated by create next app",
};

export default async function RootLayout({
  children,
}: Readonly<{
  children: React.ReactNode;
}>) {
  const flagsmithSSR = createFlagsmithInstance();
  const flagsmithState = await flagsmithSSR
    .init({
      // fetches flags on the server
      environmentID: "XRAt6CNJZdUvsMpyPeqejr", // substitute your env ID
      api: "http://localhost:8000/api/v1/",
    })
    .then(() => {
      return flagsmithSSR.getState();
    });
  return (
    <html lang="en">
      <body className={inter.className}>
        <Provider serverState={flagsmithState}>
          {children as ReactElement}
        </Provider>
      </body>
    </html>
  );
}

6-4-2. show_demo_buttonをON/OFFしてみる

あとは、先ほどと同じように、FlasSmithのダッシュボードから、show_demo_buttonをON/OFFしてみながら、http://localhost:3000/client-side-sdkにアクセスしてみて下さい!
先ほどと同じように、ボタンの表示/非表示が制御できます!

まとめ

OpenFeatureが普及すると、異なるサービスへの乗り換えも容易になるので、非常に便利になりそうだと感じました!
ただ、現時点では完璧に対応しているプロバイダが存在するのかは不明です・・・
各サービスの対応状況などを調べてみても、まだまだこれからというような印象を受けます。
これらを考慮すると、今後新規にFeature Flagを導入する場合は、OpenFeatureに参画しているサービスを採用しておいて、エコシステムが出揃ってきた段階で6-4節のようにOpenFeatureを利用するようのが安全な気がします!
利用者目線からだと、ベンダーロックインされないのは非常にありがたいので、是非OpenFeatureを採用するサービスが増えて欲しいですね!

Discussion