Slide 1

Slide 1 text

OpenFeatureと 自 動 生 成を活 用 した フィーチャーフラグの宣 言 的集約管理 株式会社サイバーエージェント 岩 見 彰太 GitHub:@BIwashi X: @B_Sardine CloudNative Days Summer 2 0 24

Slide 2

Slide 2 text

自己 紹介 岩 見 彰太 / Iwamin 株式会社サイバーエージェント ೥౓৽ଔೖࣾ "*ࣄۀຊ෦ڠۀϦςʔϧϝσΟΞ%JW アプリ運 用 カンパニー @BIwashi @B_Sardine ࣗಈੜ੒Λ׆༻ͨ͠ɺӡ༻อकίετΛ཈͑Δ&SSPS "MFSU3VOCPPLͷҰݩू໿؅ཧ 'FBUVSF'MBH%FFQ%JWF ΦϒβʔόϏϦςΟݚम࣮ફฤ 6OJ fi FE%J ff ܗࣜͷࠩ෼͔Β(P"45Λߏஙͯ͠ GFBUVSF fl BHΛࣗಈܭ૷͢Δ

Slide 3

Slide 3 text

サツドラアプリ 実はサツドラアプリは弊事業部が作ってます サイバーエージェントと協同開発した 「サツドラ公式アプリ」が2023年度グッドデザイン賞を受賞 | 株式会社サッポロドラッグストアー サイバーエージェントとサッポロドラッグストアーが協同開発した「サツドラ公式アプリ」が2023年度グッドデザイン賞を受賞 | 株式会社サイバーエージェント

Slide 4

Slide 4 text

自己 紹介 岩 見 彰太 / Iwamin 株式会社サイバーエージェント ೥౓৽ଔೖࣾ "*ࣄۀຊ෦ڠۀϦςʔϧϝσΟΞ%JW アプリ運 用 カンパニー @BIwashi @B_Sardine ࣗಈੜ੒Λ׆༻ͨ͠ɺӡ༻อकίετΛ཈͑Δ&SSPS "MFSU3VOCPPLͷҰݩू໿؅ཧ 'FBUVSF'MBH%FFQ%JWF ΦϒβʔόϏϦςΟݚम࣮ફฤ 6OJ fi FE%J ff ܗࣜͷࠩ෼͔Β(P"45Λߏஙͯ͠ GFBUVSF fl BHΛࣗಈܭ૷͢Δ

Slide 5

Slide 5 text

1 .トランクベース開発とは 2 .feature fl ag の概要 3 .feature fl ag の種類 4 .フラグ管理システム SaaS 5 .課題感 6 .OpenFeature 7 . 自 動 生 成と宣 言 的集約管理 8 .The Full Scope of the Dream Feature Flag System

Slide 6

Slide 6 text

トランクベース開発とは

Slide 7

Slide 7 text

トランクベース開発とは • 直接 main に対してどんどん merge していくスタイル • branch は1~2 日 以内に merge する短命なものでなくてはいけない • 特徴 •マージに伴うコンフリクトを最 小 限にできる •変更が加えられた main branch は常に prod に リリース可能な状態になっている •開発単位を 小 さく保ち、頻繁に main に pushし、 それに伴い CI/CD を実 行 して 自 動テストや脆弱性 診断の FBK を素早く得ることで開発スピードと デプロイまでの時間短縮を実現している トランクベース開発の trunk は Subversion というバージョン管理システム における main のことを trunk と呼んでいたらしい トランクベース開発について - 赤 帽エンジニアブログ

Slide 8

Slide 8 text

トランクベース開発とは • 直接 main に対してどんどん merge していくスタイル • branch は1~2 日 以内に merge する短命なものでなくてはいけない • 特徴 •マージに伴うコンフリクトを最 小 限にできる •変更が加えられた main branch は常に prod に リリース可能な状態になっている •開発単位を 小 さく保ち、頻繁に main に pushし、 それに伴い CI/CD を実 行 して 自 動テストや脆弱性 診断の FBK を素早く得ることで開発スピードと デプロイまでの時間短縮を実現している トランクベース開発の trunk は Subversion というバージョン管理システム における main のことを trunk と呼んでいたらしい トランクベース開発について - 赤 帽エンジニアブログ

Slide 9

Slide 9 text

feature fl ag の概要

Slide 10

Slide 10 text

feature fl ag とは • コードを変更することなくシステムの振る舞いを変更可能にする仕組み • 利点と使い所や課題 •トランクベース開発においては修正を fl ag 付きで実装しておくことで、その機能の反映可否 をコードを変更することなく 行 えるようにできる •「 fl ag 付きで実装する = fl ag を含む if 分岐で実装を囲んでおくことによっていつでもその実装を有効化無効化できるよ うに実装をしておく」 •役割を終えたら feature fl ag を削除する、といいうのを徹底してやらないとコードが複雑化 してしまう •feature fl ag の実装は属 人 化しがちなので、早めに実装した 人 が削除しないと簡単に負債化してしまう The Go gopher was designed by Renée French. GO Feature Flag

Slide 11

Slide 11 text

HOGE_FLAG := true if HOGE_FLAG == true { // flag ON ͷ࣌ʹ࣮ߦ͍ͨ͠ॲཧ // ex.) ൓ө͍ͨ͠मਖ਼ // ex.) AB ςετͷ A Λ༗ޮԽ͢Δ // ... } else { // flag OFF or flag Λઃఆ͍ͯ͠ͳ͍࣌ʹ // ࣮ߦ͍ͨ͠ॲཧ // ex.) मਖ਼લͷطଘͷ࣮૷ // ex.) AB ςετͷ B Λ༗ޮԽ͢Δ // ... } 実装イメージ • HOGE_FLAG とういう fl ag が ON だっ た場合と OFF だった場合の処理を if 文 で書く • AB テストや特定の実装を有効化する場 合などに使える The Go gopher was designed by Renée French. GO Feature Flag

Slide 12

Slide 12 text

• トランクベース開発での使 用 • A/Bテスト • カナリアリリース •特定のユーザー限定の機能の有効化 •ダークカナリアリリース •etc … feature fl ag で実現できること

Slide 13

Slide 13 text

feature fl ag の種類

Slide 14

Slide 14 text

• feature fl ag にはいくつかカテゴリがあり、それぞれの 用 途によってライフ サイクルやON/OFF の頻度などが異なる • 適切に理解して使 用 する必要がある • (feature fl ag と feature toggle という2つの呼び名が存在するが 同じもの) feature fl ag の種類 The Go gopher was designed by Renée French. Feature Toggles (aka Feature Flags)

Slide 15

Slide 15 text

• トランクベース開発を可能にするために使 用 • 進 行 中の機能を main に merge して、いつでも本番環境に展開できるように する • 1~2週間以上存在してはいけないことが推奨 されている • 基本的に静的 request単位などでON/OFFが切り替わらない Release Toggles

Slide 16

Slide 16 text

• A/Bテストやカナリアリリースを実現するために使 用 • 仮説検証 用 の実装や 一 部のユーザーに限定して機能を公開する場合などに使 用 される • データ駆動型の最適化に使 用 される • A/Bテストなどの場合は有意なデータを収集する ために 十 分な期間が必要だが、システムに変更 を加えてしまうとその影響が載ってしまう可能性 があるため、時間や週単位が推奨されている Experiment Toggles

Slide 17

Slide 17 text

• システム動作の運 用面 の制御に使 用 • パフォーマンスへの影響が不明確な新機能をロールアウトする際に導 入 し、 必要に応じて無効化や低下をできるようにする • 障害発 生 時のリカバリー 用 として使 用 する • テクニックとして、システム全体の負荷が 高 まっ てしまった際に重要ではない機能を OFF にして 対応する 「Circuit Breaker」としての機能も あるらしい Ops Toggles

Slide 18

Slide 18 text

• 特定のユーサーのみ機能を加えたり、特別な機能を解放する場合などに使 用 • 課 金 ユーザーや PoC の対象者に限定的に機能を解放するなど • Experiment Toggles が仮説検証のA/Bテストなどの 用 途であったのに対し、 Permission Toggles はすでに完成している 機能の限定公開の 用 途として使 用 される Permission Toggles

Slide 19

Slide 19 text

フラグ管理システム SaaS

Slide 20

Slide 20 text

0QFO'FBUVSFBTUBOEBSEGPSGFBUVSF fl BHHJOHc0QFO'FBUVSF

Slide 21

Slide 21 text

• feature fl ag の状態をリモート管理してくれる • feature fl ag の状態を管理する 方 法は 色 々ある 環境変数 json などの静的ファイル • feature fl ag の値を管理し、リアルタイムでの変更や context 情報から適切に値を決定す るなどを実現するサービスが存在 • これらは基本的にSDK(API)になっており、 fl ag 名と contex 情報(ターゲティングなど をする際のユーザー情報やリクエスト情報など)を渡して、その結果として評価値を返す フラグ管理システム(以下FFSaaS)

Slide 22

Slide 22 text

課題感

Slide 23

Slide 23 text

• SDK の実装は各サービス違う 変更したくなった場合には使 用 している実装部分 を全て修正する必要 • 開発のフェーズやニーズによって速度や求 められる機能が異なる 置き換えたくなる需要がそこそこ発 生 する可能性 • 移 行 が 大 変 置き換えした際にデグレする可能性もある • 複数兼 用 する可能性もある 複数の SDK を使いこなす必要がある 依存などがあった場合は 大 変 ベンダーロックイン import configcat "github.com/configcat/go-sdk/v9" client := configcat.NewClient("#YOUR-SDK-KEY#") enabled := client.GetBoolValue("flagName", false, nil) if enabled { doTheNewThing() } $PO fi H$BU import flagsmith "github.com/Flagsmith/flagsmith-go-client/v3" client := flagsmith.NewClient(os.Getenv("#YOUR-SDK-KEY#")) flags, _ := client.GetEnvironmentFlags(ctx) enabled, _ := flags.IsFeatureEnabled("flagName") if enabled { doTheNewThing() } 'MBHTNJUI

Slide 24

Slide 24 text

• SDK の実装は各サービス違う 変更したくなった場合には使 用 している実装部分 を全て修正する必要 • 開発のフェーズやニーズによって速度や求 められる機能が異なる 置き換えたくなる需要がそこそこ発 生 する可能性 • 移 行 が 大 変 置き換えした際にデグレする可能性もある • 複数兼 用 する可能性もある 複数の SDK を使いこなす必要がある 依存などがあった場合は 大 変 ベンダーロックイン import configcat "github.com/configcat/go-sdk/v9" client := configcat.NewClient("#YOUR-SDK-KEY#") enabled := client.GetBoolValue("flagName", false, nil) if enabled { doTheNewThing() } $PO fi H$BU import flagsmith "github.com/Flagsmith/flagsmith-go-client/v3" client := flagsmith.NewClient(os.Getenv("#YOUR-SDK-KEY#")) flags, _ := client.GetEnvironmentFlags(ctx) enabled, _ := flags.IsFeatureEnabled("flagName") if enabled { doTheNewThing() } 'MBHTNJUI GetBoolValue という関数で bool を取得 GetEnvironmentFlags という関数で fl ags を取得 IsFeatureEnabled という関数で bool 取得

Slide 25

Slide 25 text

OpenFeature

Slide 26

Slide 26 text

• 2022年5 月 に公開された 比 較的新しい OSS CNCF の Incubating Project にも採択 • feature fl ag 管理のオープン標準規格 • 直接的な feature fl ag を管理する OSS ではなくあくまで抽象化層 • Provider を介して、対応している feature fl ag service と繋ぐことができる OpenFeature Provider • Provider の interface を実装すると任意の custom provider を作れる ex. 環境変数、S 3 、 自 前サーバー etc … • Dynatrace が主導している OpenFeature とは 0QFO'FBUVSFc$/$'

Slide 27

Slide 27 text

• ベンダーロックインを避けられる アプリケーションの実装部分が変わらない 好きな FFSaaS を気軽に乗り換えられる 複数のツールを使うこともできる • 環境や種類によって FFSaaS を使い分けられる 環境 dev、stg では頻繁に fl ag を 入 れ替えるため、便利さのために FFSaaS を使 用 するが、本番環境ではリードタイムを許容できるのと 金 銭 面 から環境変数によって更新する 種類 ターゲティングがなく、特に context 情報などを必要としない Release Toggle などは環境変数を使 用 特定の条件の 人 に出し分ける AB テストや限定公開の条件をある程度 長 期的に更新する必要があるような Experiment Toggle や Permission Toggle などでは FFSaaS を使う • feature fl ag の実装箇所は変えることなく Provider を差し替えるのみ OpenFeature のなにが嬉しいか

Slide 28

Slide 28 text

OpenFeature のなにが嬉しいか OpenFeature: 標準 API を定義し共通の SDK を提供 ベンダー: プロバイダーの開発に集中 アプリケーション開発: 共通の SDK を使 用 して実装し Provider を付け替えるだけ OpenFeature - a standard for feature fl agging | OpenFeature

Slide 29

Slide 29 text

OpenFeature Concept

Slide 30

Slide 30 text

• アプリケーション実装者が操作する主要 コンポーネント • 手 順 Provider 設定 Client 作成 Flag 評価 • Flag 評価値は2種類 Basic Evaluation Detailed Evaluation Evaluation API &WBMVBUJPO"1*c0QFO'FBUVSF &WBMVBUJPO%FUBJMT4USVDUVSF'JFMET 5ZQFTBOE%BUB4USVDUVSFTc0QFO'FBUVSF &SSPS$PEF

Slide 31

Slide 31 text

1SPWJEFSTc0QFO'FBUVSF • フラグ管理システムと OpenFeature SDK の間の抽象化層 ベンダーの SDK をラップ フラグ評価 用 の REST API を叩く ローカルストレージから取得 Providers

Slide 32

Slide 32 text

// set a value to the global context openfeature.SetEvaluationContext(openfeature.NewTargetlessEvaluationContext( map[string]interface{}{ "region": "us-east-1-iah-1a", }, )) // set a value to the client context client := openfeature.NewClient("my-app") client.SetEvaluationContext(openfeature.NewTargetlessEvaluationContext( map[string]interface{}{ "version": "1.4.6", }, )) // set a value to the invocation context evalCtx := openfeature.NewEvaluationContext( "user-123", map[string]interface{}{ "company": "Initech", }, ) boolValue, err := client.BooleanValue("boolFlag", false, evalCtx) EvaluationContext • 動的評価 用 のコンテキストデータ ホスト アプリケーション識別 子 IPアドレス etc … • フラグ評価に対して暗黙的 ・ 明 示 的に渡すことができる

Slide 33

Slide 33 text

// set a value to the global context openfeature.SetEvaluationContext(openfeature.NewTargetlessEvaluationContext( map[string]interface{}{ "region": "us-east-1-iah-1a", }, )) // set a value to the client context client := openfeature.NewClient("my-app") client.SetEvaluationContext(openfeature.NewTargetlessEvaluationContext( map[string]interface{}{ "version": "1.4.6", }, )) // set a value to the invocation context evalCtx := openfeature.NewEvaluationContext( "user-123", map[string]interface{}{ "company": "Initech", }, ) boolValue, err := client.BooleanValue("boolFlag", false, evalCtx) EvaluationContext • 動的評価 用 のコンテキストデータ ホスト アプリケーション識別 子 IPアドレス etc … • フラグ評価に対して暗黙的 ・ 明 示 的に渡すことができる

Slide 34

Slide 34 text

)PPLTc0QFO'FBUVSF • フラグ評価サイクルの明確に定義されたポイントで任意の動作を追加できるよ うにするメカニズム • 使 用用 途 コンテキストへのデータ追加 ログ記録 テレメトリ • OpenTelemetry 用 の Hooks なども存在 Hooks

Slide 35

Slide 35 text

&WFOUTc0QFO'FBUVSF • プロバイダーまたはフラグ管理システムの状態の取得対応 • 検知可能な状態 フラグの定義変更 新フラグの追加や既存フラグの変更 プロバイダーの準備完了 プロバイダーが使 用 可能になったことの通知 エラー状態 フラグの評価やプロバイダーとの通信でのエラー発 生 • プロバイダーの役割 イベント発 行 特定イベントを検知したことを 示 すコールバック実 行 オプションでイベントデータの提供 • クライアント or グローバルAPIハンドラーの役割 発 行 されたイベントデータを使 用 して処理を実 行 Events

Slide 36

Slide 36 text

自 動 生 成と宣 言 集約管理

Slide 37

Slide 37 text

課題感 • FFSaaS を使 用 した場合フラグ情報が分散してしまう • 問題点 typo の検出ができない FFSaaSに現存する fl ag の全容が把握しにくい( fl ag のステータスではなく存在するか)

Slide 38

Slide 38 text

課題感

Slide 39

Slide 39 text

課題感

Slide 40

Slide 40 text

課題感 アプリケーション実装で使 用

Slide 41

Slide 41 text

課題感 • FFSaaSを使 用 した場合に情報が分散してしまう • 問題点 typo の検出ができない FFSaaSに現存する fl ag の全容が把握しにくい( fl ag のステータスではなく存在するか)

Slide 42

Slide 42 text

課題感 • FFSaaSを使 用 した場合に情報が分散してしまう • 問題点 typo の検出ができない FFSaaSに現存する fl ag の全容が把握しにくい( fl ag のステータスではなく存在するか) アプリケーションコードとTerraform(FFSaaS) は分離してしまう

Slide 43

Slide 43 text

自 動 生 成を活 用 して 宣 言 的に集約管理する

Slide 44

Slide 44 text

宣 言 管理 ・ 集約管理のモチベーション • FFSaaS、アプリケーション、Terraform に fl ag 情報を書く必要があるがどれも 本質は同じもの 同じ情報なのであれば集約して管理したい • GitOps として宣 言 的に管理したい GitOps で全て管理したい feature fl ag の情報も Git 上で管理するようにしたい 作成はまだしも、削除し忘れが多いのでそれをしっかり認識できるようにしたい • fl ag について記述された 一 つの場所から、 用 途に合わせて 色 々なものを 自 動 生 成 したい feature fl ag エコシステムの開発をしたい

Slide 45

Slide 45 text

Go Generate!! The Go gopher was designed by Renée French. Created using gopherize.me

Slide 46

Slide 46 text

No content

Slide 47

Slide 47 text

Protoc Plugin • Protocol Bu ff ers に記述された情報を使 用 して 自 動 生 成などを 行 う • Extend を使 用 して message や fi eld に情報を 追加したり拡張することができる • 今回は protoc plugin をサクッと書ける protoc-gen-star(PG*)を使 用 MZGUQSPUPDHFOTUBSQSPUPDQMVHJOMJCSBSZGPSF ffi DJFOUQSPUPCBTFEDPEFHFOFSBUJPO Go Conference mini 2 023 Winter in KYOTO %FW0QT%BZT50,:0

Slide 48

Slide 48 text

Protoc Plugin • すでにPJTでは 大 量の protoc plugin がある • ここに新しく追加する形 自 動 生 成されたファイル数 1 , 9 6 8 自 動 生 成された 行 数 338 , 667

Slide 49

Slide 49 text

No content

Slide 50

Slide 50 text

syntax = "proto3"; package featureflag; import "ext/feature_flag.proto"; option (ext.feature_flag_config) = { configcat: { env_names: [ "stg", "prod" ] } flagsmith: { env_names: [ "dev" ] } }; message Release { option (ext.feature_flag_message).default_expiry_days = 7; bool my_first_test_bool = 1; bool my_second_test_bool = 2 [(ext.feature_flag_field).expiry_days = 30]; string my_first_test_string = 3; int32 my_first_test_int = 4; float my_first_test_float = 5; } message Experiment { bool my_first = 1 [(ext.feature_flag_field).expiry_days = 14]; } message Ops { option (ext.feature_flag_message).default_expiry_days = 90; bool my_first = 1; } message Permission { bool my_first = 1; } feature fl ag proto fi le

Slide 51

Slide 51 text

syntax = "proto3"; package featureflag; import "ext/feature_flag.proto"; option (ext.feature_flag_config) = { configcat: { env_names: [ "stg", "prod" ] } flagsmith: { env_names: [ "dev" ] } }; message Release { option (ext.feature_flag_message).default_expiry_days = 7; bool my_first_test_bool = 1; bool my_second_test_bool = 2 [(ext.feature_flag_field).expiry_days = 30]; string my_first_test_string = 3; int32 my_first_test_int = 4; float my_first_test_float = 5; } message Experiment { bool my_first = 1 [(ext.feature_flag_field).expiry_days = 14]; } message Ops { option (ext.feature_flag_message).default_expiry_days = 90; bool my_first = 1; } message Permission { bool my_first = 1; } feature fl ag proto fi le

Slide 52

Slide 52 text

option (ext.feature_flag_config) = { configcat: { env_names: [ "stg", "prod" ] } flagsmith: { env_names: [ "dev" ] } }; feature fl ag proto fi le • 作成する terraform 用 の情報 • 各FFSaaSで必要な情報を 入 れる

Slide 53

Slide 53 text

syntax = "proto3"; package featureflag; import "ext/feature_flag.proto"; option (ext.feature_flag_config) = { configcat: { env_names: [ "stg", "prod" ] } flagsmith: { env_names: [ "dev" ] } }; message Release { option (ext.feature_flag_message).default_expiry_days = 7; bool my_first_test_bool = 1; bool my_second_test_bool = 2 [(ext.feature_flag_field).expiry_days = 30]; string my_first_test_string = 3; int32 my_first_test_int = 4; float my_first_test_float = 5; } message Experiment { bool my_first = 1 [(ext.feature_flag_field).expiry_days = 14]; } message Ops { option (ext.feature_flag_message).default_expiry_days = 90; bool my_first = 1; } message Permission { bool my_first = 1; } feature fl ag proto fi le

Slide 54

Slide 54 text

message Release { option (ext.feature_flag_message).default_expiry_days = 7; bool my_first_test_bool = 1; bool my_second_test_bool = 2 [(ext.feature_flag_field).expiry_days = 30]; string my_first_test_string = 3; int32 my_first_test_int = 4; float my_first_test_float = 5; } message Experiment { bool my_first = 1 [(ext.feature_flag_field).expiry_days = 14]; } message Ops { option (ext.feature_flag_message).default_expiry_days = 90; bool my_first = 1; } message Permission { bool my_first = 1; } feature fl ag proto fi le • fl ag のタイプごとに fl agを記述 • bool以外の型を使いたい場合は string や int 32 などを使 用 • expiry days は reminder test で 使 用 する(後述)

Slide 55

Slide 55 text

No content

Slide 56

Slide 56 text

// Code generated by protoc-gen-go-feature-flag. DO NOT EDIT. // source: go.tmpl package copenfeature type FeatureFlag string const ( ReleaseMyFirstTestBool FeatureFlag = "RELEASE_MY_FIRST_TEST_BOOL" ReleaseMySecondTestBool FeatureFlag = "RELEASE_MY_SECOND_TEST_BOOL" ReleaseMyFirstTestString FeatureFlag = "RELEASE_MY_FIRST_TEST_STRING" ReleaseMyFirstTestInt FeatureFlag = "RELEASE_MY_FIRST_TEST_INT" ReleaseMyFirstTestFloat FeatureFlag = "RELEASE_MY_FIRST_TEST_FLOAT" ExperimentMyFirst FeatureFlag = "EXPERIMENT_MY_FIRST" OpsMyFirst FeatureFlag = "OPS_MY_FIRST" PermissionMyFirst FeatureFlag = “PERMISSION_MY_FIRST" ) feature fl ag for application • feature fl ag 実装部分で使うようの fl ag を定数で定義 • 自 動 生 成することでコピペなどによ る事故を回避 • 今回は Go を 生 成したが別 言 語でも 可能 fronend/native も同様に使 用 可

Slide 57

Slide 57 text

if copenfeature.BooleanValue(ctx, copenfeature.ReleaseMyFirstTestBool) { fmt.Println("my_first_test_bool is true") } else { fmt.Println("my_first_test_bool is false") } 生 成された fl agを使う

Slide 58

Slide 58 text

if copenfeature.BooleanValue(ctx, copenfeature.ReleaseMyFirstTestBool) { fmt.Println("my_first_test_bool is true") } else { fmt.Println("my_first_test_bool is false") } 生 成された fl agを使う • 生 成された定数を指定すればいい • 定数も 自 動 生 成されており、ソースも同じなので 一 致が保証される

Slide 59

Slide 59 text

No content

Slide 60

Slide 60 text

// Code generated by protoc-gen-go-feature-flag. DO NOT EDIT. // source: config_cat.tmpl resource "configcat_setting" "releasemy_first_test_bool" { config_id = configcat_config.test_config.id key = "RELEASEMY_FIRST_TEST_BOOL" name = "ReleaseMyFirstTestBool" setting_type = "boolean" order = 0 } Terraform • FFSaaS に fl ag を追加する Terraform を 生 成 • アプリケーション 用 の実装(Go)と 一 致が保証 • 複数の FFSaaS 用 の Terraform を 生 成すれば変更が容易 OpenFeature Provider を付け替えればすぐさま使 用 可能

Slide 61

Slide 61 text

Terraform で作成

Slide 62

Slide 62 text

No content

Slide 63

Slide 63 text

Application OpenFeature を採 用 しているので OpenFeature Provider を変えるだけ FFSaaS 自 動 生 成しているので 生 成する Terraform を変えるだけ

Slide 64

Slide 64 text

料 金 や機能によって乗り換える 環境によって使い分ける マイクロサービスごとに分ける

Slide 65

Slide 65 text

No content

Slide 66

Slide 66 text

message Release { option (ext.feature_flag_message).default_expiry_days = 7; bool my_first_test_bool = 1; bool my_second_test_bool = 2 [(ext.feature_flag_field).expiry_days = 30]; string my_first_test_string = 3; int32 my_first_test_int = 4; float my_first_test_float = 5; } message Experiment { bool my_first = 1 [(ext.feature_flag_field).expiry_days = 14]; } message Ops { option (ext.feature_flag_message).default_expiry_days = 90; bool my_first = 1; } message Permissioning { bool my_first = 1; } feature fl ag の削除 • feature fl ag の削除は徹底してやら ないとコードが複雑化してしまう • 作成後 一 定期間経過したら通知する ようにしたい • 一 定期間経過したら落ちるテストを 自 動 生 成して CI で検知する

Slide 67

Slide 67 text

message Release { option (ext.feature_flag_message).default_expiry_days = 7; bool my_first_test_bool = 1; bool my_second_test_bool = 2 [(ext.feature_flag_field).expiry_days = 30]; string my_first_test_string = 3; int32 my_first_test_int = 4; float my_first_test_float = 5; } message Experiment { bool my_first = 1 [(ext.feature_flag_field).expiry_days = 14]; } message Ops { option (ext.feature_flag_message).default_expiry_days = 90; bool my_first = 1; } message Permissioning { bool my_first = 1; } feature fl ag の削除 通知するまでに 日 数を Proto File で指定する • feature fl ag の削除は徹底してやら ないとコードが複雑化してしまう • 作成後 一 定期間経過したら通知する ようにしたい • 一 定期間経過したら落ちるテストを 自 動 生 成して CI で検知する

Slide 68

Slide 68 text

feature fl ag の削除 // Code generated by protoc-gen-go-feature-flag. DO NOT EDIT. // source: reminder.tmpl package reminder import ( "testing" "time" "github.com/stretchr/testify/assert" "github.com/BIwashi/test/backend/pkg/copenfeature" "github.com/BIwashi/test/backend/pkg/ctime" ) var ( releaseMyFirstTestBoolCreationDate = time.Date(2024, 6, 12, 22, 44, 23, 271402523, ctime.JSTLocation) releaseMyFirstTestBoolExpiryDate = releaseMyFirstTestBoolCreationDate.AddDate(0, 0, 7) ) func TestReleaseMyFirstTestBoolFlagExpiry(t *testing.T) { _ = copenfeature.ReleaseMyFirstTestBool t.Run("MyFirstTestBoolFlag", func(t *testing.T) { assert.True(t, time.Now().In(ctime.JSTLocation).Before(releaseMyFirstTestBoolExpiryDate)) }) }

Slide 69

Slide 69 text

feature fl ag の削除 // Code generated by protoc-gen-go-feature-flag. DO NOT EDIT. // source: reminder.tmpl package reminder import ( "testing" "time" "github.com/stretchr/testify/assert" "github.com/BIwashi/test/backend/pkg/copenfeature" "github.com/BIwashi/test/backend/pkg/ctime" ) var ( releaseMyFirstTestBoolCreationDate = time.Date(2024, 6, 12, 22, 44, 23, 271402523, ctime.JSTLocation) releaseMyFirstTestBoolExpiryDate = releaseMyFirstTestBoolCreationDate.AddDate(0, 0, 7) ) func TestReleaseMyFirstTestBoolFlagExpiry(t *testing.T) { _ = copenfeature.ReleaseMyFirstTestBool t.Run("MyFirstTestBoolFlag", func(t *testing.T) { assert.True(t, time.Now().In(ctime.JSTLocation).Before(releaseMyFirstTestBoolExpiryDate)) }) } フラグ作成 日 時と 通知の来る 日 時を指定

Slide 70

Slide 70 text

feature fl ag の削除 // Code generated by protoc-gen-go-feature-flag. DO NOT EDIT. // source: reminder.tmpl package reminder import ( "testing" "time" "github.com/stretchr/testify/assert" "github.com/BIwashi/test/backend/pkg/copenfeature" "github.com/BIwashi/test/backend/pkg/ctime" ) var ( releaseMyFirstTestBoolCreationDate = time.Date(2024, 6, 12, 22, 44, 23, 271402523, ctime.JSTLocation) releaseMyFirstTestBoolExpiryDate = releaseMyFirstTestBoolCreationDate.AddDate(0, 0, 7) ) func TestReleaseMyFirstTestBoolFlagExpiry(t *testing.T) { _ = copenfeature.ReleaseMyFirstTestBool t.Run("MyFirstTestBoolFlag", func(t *testing.T) { assert.True(t, time.Now().In(ctime.JSTLocation).Before(releaseMyFirstTestBoolExpiryDate)) }) } protoから削除(定数が削除)されたら テストファイルを 手 動削除する 忘れていたらコンパイルエラーで気づけるように

Slide 71

Slide 71 text

feature fl ag の削除 // Code generated by protoc-gen-go-feature-flag. DO NOT EDIT. // source: reminder.tmpl package reminder import ( "testing" "time" "github.com/stretchr/testify/assert" "github.com/BIwashi/test/backend/pkg/copenfeature" "github.com/BIwashi/test/backend/pkg/ctime" ) var ( releaseMyFirstTestBoolCreationDate = time.Date(2024, 6, 12, 22, 44, 23, 271402523, ctime.JSTLocation) releaseMyFirstTestBoolExpiryDate = releaseMyFirstTestBoolCreationDate.AddDate(0, 0, 7) ) func TestReleaseMyFirstTestBoolFlagExpiry(t *testing.T) { _ = copenfeature.ReleaseMyFirstTestBool t.Run("MyFirstTestBoolFlag", func(t *testing.T) { assert.True(t, time.Now().In(ctime.JSTLocation).Before(releaseMyFirstTestBoolExpiryDate)) }) } 現在時刻が通知 日 時を過ぎていたらテストが失敗 CIで検知できるようにする

Slide 72

Slide 72 text

No content

Slide 73

Slide 73 text

# Code generated by protoc-gen-go-feature-flag. DO NOT EDIT. - name: "ff/ReleaseMyFirstTestBool" color: "000000" description: "Release flag" - name: "ff/ReleaseMySecondTestBool" color: "000000" description: "Release flag" - name: "ff/ReleaseMyFirstTestString" color: "000000" description: "Release flag" - name: "ff/ReleaseMyFirstTestInt" color: "000000" description: "Release flag" - name: "ff/ReleaseMyFirstTestFloat" color: "000000" description: "Release flag" - name: "ff/ExperimentMyFirst" color: "000000" description: "Experiment flag" - name: "ff/OpsMyFirst" color: "000000" description: "Ops flag" - name: "ff/PermissioningMyFirst" color: "000000" description: "Permissioning flag" PR ラベル • feature fl ag を削除する際に追加した時の実装を 見 たい • 削除ライフサイクル(後述)対策として、リリー スされたバージョンに含まれている feature fl ag を知りたい • ghactions-github-labeler で 自 動 生 成した yaml からラベルを作成 yaml の git di ff 差分からラベルを PR に付与 DSB[ZNBYHIBDUJPOHJUIVCMBCFMFS(JU)VC"DUJPOUP NBOBHFMBCFMTPO(JU)VC

Slide 74

Slide 74 text

PR ラベル • PR にフラグ名のラベルが付けられた • release note でも振り分けたられた QJQFDEBDUJPOTHISFMFBTF"OBDUJPOUIBUFOBCMFTPQFSBUJOH (JU)VCSFMFBTFWJBQVMMSFRVFTU

Slide 75

Slide 75 text

削除ライフサイクルの課題

Slide 76

Slide 76 text

ライフサイクルの違い • FFSaaS に対しての操作は基本的に全環境 一 括で 行 われる • アプリケーションコードは各環境ごとに 行 われる • FFSaaS から削除する際はアプリケーションコードから feature fl ag が削除 された version が prod まで上がって初めて可能になる 削除のライフサイクルが異なる 適切に削除するには削除ライフサイクルを考慮して設計する必要がある

Slide 77

Slide 77 text

No content

Slide 78

Slide 78 text

アプリケーションとFFSaaSは 同時に作成

Slide 79

Slide 79 text

prod まで fl ag のアプリケーション実装の version が上がると削除できる この時点では prod で fl ag を使 用 しているので FFSaaS からは削除してはいけない

Slide 80

Slide 80 text

prod まで fl ag 削除の実装の version が上がると削除できる

Slide 81

Slide 81 text

The Full Scope of the Dream Feature Flag System

Slide 82

Slide 82 text

まだ夢の話です • 実装中のものと 一 部アイデア段階のものを少し紹介します The Go gopher was designed by Renée French.

Slide 83

Slide 83 text

No content

Slide 84

Slide 84 text

6OJ fi FE%J ff ܗࣜͷࠩ෼͔Β(P"45Λߏஙͯ͠GFBUVSF fl BHΛࣗಈܭ૷͢Δ 追加 git di ff から feature fl ag を 自 動計装する

Slide 85

Slide 85 text

No content

Slide 86

Slide 86 text

Release Note: v 1 . 0 . 0 • [ADD] Add fl ag A and … • [ADD] Add fl ag X and … • [DELETE] Remove fl ag Y … • hoge • foo • piyo ff / fl agA ff / fl agX ff / fl agY PRに fl ag 名のラベルを 自 動でつける タイトルに [ADD] or [DELETE] をつける

Slide 87

Slide 87 text

Release Note: v 1 . 0 . 0 • [ADD] Add fl ag A and … • [ADD] Add fl ag X and … • [DELETE] Remove fl ag Y … • hoge • foo • piyo ff / fl agA ff / fl agX ff / fl agY prodまで 行 ったらその version を送信

Slide 88

Slide 88 text

Release Note: v 1 . 0 . 0 • [ADD] Add fl ag A and … • [ADD] Add fl ag X and … • [DELETE] Remove fl ag Y … • hoge • foo • piyo ff / fl agA ff / fl agX ff / fl agY [ADD] がついている PR を探す フラグ名をラベルから取得する

Slide 89

Slide 89 text

Release Note: v 1 . 0 . 0 • [ADD] Add fl ag A and … • [ADD] Add fl ag X and … • [DELETE] Remove fl ag Y … • hoge • foo • piyo ff / fl agA ff / fl agX ff / fl agY feature fl ag を 自 動削除する (Pͷ"45Λղੳͯ͠'FBUVSF5PHHMFΛ૟আ͢ΔGSFFF%FWFMPQFST)VC

Slide 90

Slide 90 text

Release Note: v 1 . 0 . 0 • [ADD] Add fl ag A and … • [ADD] Add fl ag X and … • [DELETE] Remove fl ag Y … • hoge • foo • piyo ff / fl agA ff / fl agX ff / fl agY Release Note: v 2 . 0 . 0 • [DELETE] Remove fl ag A ff / fl agA [DELETE] をタイトルにつけて 生 成

Slide 91

Slide 91 text

Release Note: v 1 . 0 . 0 • [ADD] Add fl ag A and … • [ADD] Add fl ag X and … • [DELETE] Remove fl ag Y … • hoge • foo • piyo ff / fl agA ff / fl agX ff / fl agY Release Note: v 2 . 0 . 0 • [DELETE] Remove fl ag A ff / fl agA prodまで 行 ったらその version を送信

Slide 92

Slide 92 text

Release Note: v 1 . 0 . 0 • [ADD] Add fl ag A and … • [ADD] Add fl ag X and … • [DELETE] Remove fl ag Y … • hoge • foo • piyo ff / fl agA ff / fl agX ff / fl agY Release Note: v 2 . 0 . 0 • [DELETE] Remove fl ag A ff / fl agA [DELETE] がついている PR を探す フラグ名をラベルから取得する

Slide 93

Slide 93 text

Release Note: v 1 . 0 . 0 • [ADD] Add fl ag A and … • [ADD] Add fl ag X and … • [DELETE] Remove fl ag Y … • hoge • foo • piyo ff / fl agA ff / fl agX ff / fl agY Release Note: v 2 . 0 . 0 • [DELETE] Remove fl ag A ff / fl agA フラグ名の terraform fi le を削除

Slide 94

Slide 94 text

まとめ • OpenFeature と protoc plugin 活 用 して、 自 由にフラグ管理システムを差 し替えられるようにした • protoc plugin を活 用 して proto fi le に情報を集約することによって情報を 宣 言 集約管理できた アプリケーションコード インフラコード リマインダーテストコード PRラベラー • feature fl ag を意識しなくてもいい feature fl ag エコシステムを構築したい The Go gopher was designed by Renée French. GO Feature Flag

Slide 95

Slide 95 text

Appendix

Slide 96

Slide 96 text

実際の OpenFeature の実装例

Slide 97

Slide 97 text

fval, err := client.ofc.BooleanValue(ctx, "testFlag", false, openfeature.EvaluationContext{}) if err != nil { logger.Error(ctx, "failed to get boolean value" } OpenFeature の具体実装例 for Go • OpenFeature を素で使った場合 毎回引数に 入 れないといけないものが多くて少し可読性が悪い デフォルト値、evaluation context 毎回必要なわけではないので必要な時だけ 入 れるようにしたい

Slide 98

Slide 98 text

func BooleanValue(ctx context.Context, flag FeatureFlag, opts ...Option[bool]) bool { var ( err error opt = options[bool]{ evaluationContext: ctxkey.GetOpenFeatureEvaluationContext(ctx), defaultValue: false, } ) span, ctx := trace.StartSpan( ctx, fmt.Sprintf("copenfeature.BooleanValue.%s", flag), ) defer func() { span.Finish(err) }() for _, o := range opts { o(&opt) } eflag, err := client.ofc.BooleanValue( ctx, string(flag), opt.defaultValue, opt.evaluationContext, opt.ofcOpts..., ) if err != nil { errorLogger(ctx, "failed to get boolean value", flag, err) return opt.defaultValue } return eflag } 少しラップ • functional option patternで 入 れ られるように少しラップしている • span も仕込んでいる

Slide 99

Slide 99 text

func EvaluationContextUnaryServerInterceptor(logger log.Logger) grpc.UnaryServerInterceptor { return func( ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler, ) (interface{}, error) { ai, ed := extractInfoFromMetadata(ctx) userID, evalCustomData := getUserInfo(ctx, info) evaluation := map[string]interface{}{ "appVersion": ai.xAppVesion, "appBuild": ai.xAppBuildNumber, "deviceModel": ed.xDeviceModel, "customData": evalCustomData, } ctx = ctxkey.SetOpenFeatureEvaluationContext( ctx, copenfeature.NewEvaluationContext( userID, evaluation, ), ) return handler(ctx, req) } } EvaluationContext • gRPC Interceptor で取得した動 的評価表のデータを context に 入 れておく • ラップした関数ではこの context から取り出したデータを 使 用 するように実装しておく • Option が 入 れられた際は Overwrite する

Slide 100

Slide 100 text

Option • Option にはジェネリクスを使 用 defaultValue に型制約をつける func BooleanValue(ctx context.Context, flag FeatureFlag, opts ...Option[bool]) bool { ... return eflag } type options[T any] struct { evaluationContext of.EvaluationContext ofcOpts []of.Option defaultValue T } type Option[T any] func(*options[T]) func WithEvaluationContext[T any](ctx of.EvaluationContext) Option[T] { return func(o *options[T]) { o.evaluationContext = ctx } } func WithOFClientOptions[T any](opts ...of.Option) Option[T] { return func(o *options[T]) { o.ofcOpts = opts } } func WithDefaultValue[T any](v T) Option[T] { return func(o *options[T]) { o.defaultValue = v } }

Slide 101

Slide 101 text

if copenfeature.BooleanValue( ctx, copenfeature.TestFlag, copenfeature.WithDefaultValue(true), copenfeature.WithEvaluationContext[bool](copenfeature.NewEvaluationContext("", nil)), ) { … } Option copenfeature.WithDefaultValue("string"), Bool 以外の型の値を default value に 入 れようとすると怒られる