こちらは「コドモン Advent Calendar 2025🎄」の 6 日目の記事になります。
はじめに
こんにちは。プロダクト開発部の河野です。
普段はコドモンの写真販売・共有機能を担当しているチームで、プロダクトの改善や新機能の開発を行っています。
先日、私たちのチームでは写真販売の新機能として「せんせいフォト連携機能」をリリースしました。
本記事では、このリリースで直面したロールアウトの課題と、その解決策についてお話しします。
背景
今回のリリースにあたっては、以下の要件がありました。
- 公開日を厳守したい
- ステークホルダーが多く、プレスリリースでの周知もあるため公開日の厳守が必要
- 段階的なリリース
- 一気にすべての施設に公開するのではなく、段階的な展開が必要
- 複雑な公開条件(後述)
- 施設の契約プランや施設種別などによる細かな公開制御が必要
- 公開後も特定施設への手動公開の運用
- 一般公開後も、特定の施設に対して手動で公開・非公開を切り替える運用が必要
公開条件とリリーススケジュールの詳細
公開条件とスケジュール(一部省略)は以下のように決まっていました。
リリーススケジュール
- 5/21 〜 段階リリース開始
- 指定した施設に対してのみ公開
- 第 1 弾〜第 4 弾
- 1 週間おきに順次公開範囲を拡大
- 7/16 一般公開
- 条件を満たす全施設に対して公開
- 7/16 ~ 一般公開後
- 特定の施設に対して手動で公開・非公開を切り替える運用が必要
公開制御の条件
一般公開時は、以下の条件で公開を制御する必要がありました。
- 特定の契約プラン → 非公開
- 特定の施設 → 非公開
- 自治体かつ指定された特定の施設 → ホワイトリスト形式(指定施設のみ公開、一般公開後も順次追加)
- 販売パートナー経由かつ指定された特定の施設 → ホワイトリスト形式(指定施設のみ公開、一般公開後も順次追加)
- 上記の条件に該当しない施設は全て公開
※ 各条件については、実際には契約内容や施設の属性など複数条件の組み合わせによって決まるものですが、ここでは表記を簡略化しています。
公開要件を満たすためのアプローチ
公開日を厳守しつつ、安全に段階的な公開を進めていくためには、ロールアウトとソースコードのデプロイを切り分けることが必須だと考えました。
その上で、ロールアウトの実装方法として、以下二つのアプローチを検討しました。
- AWS AppConfig(以下 AppConfig) を利用する方法
- データベースで制御する方法
AppConfig を利用する方法
AppConfig は AWS が提供しているアプリケーションの設定情報を管理・配信するサービスです。
当該サービスの Feature Flags 機能を利用することで、アプリケーションの機能のオンオフを柔軟に制御できます。
また、段階的なロールアウトや、CloudWatch アラームと連携した自動ロールバックなどの機能もサポートしています。
AWS AppConfig Feature Flags
Feature Flags には以下の機能があります。
- 単一フラグ
- マルチバリアント機能
単一フラグ
単一フラグは、特定の機能をオンオフするためのシンプルなフラグです。
例えば、AWS コンソール側で以下のようなフラグを設定します。

これは、単一フラグのテストという機能名で、true/false の値を持つフラグを定義しています。
フラグキーを simpleFlags としているため、アプリケーション側ではこのキーを使ってフラグの状態を取得できます。
フラグの取得方法は、AWS SDK を利用する方法と、ECS であれば AppConfig エージェントのサイドカーを利用して、localhost 経由で取得する方法があります。
詳しくはAWS AppConfig エージェントを使用して設定データを取得する方法をご参照ください。
フラグ情報は以下のような JSON 形式で取得できます。
{ "enabled": true }
これらを利用すれば、ソースコードの変更なしに、特定の機能をオンオフできるようになります。
マルチバリアント機能
マルチバリアント機能は、単一フラグよりも複雑な条件でフラグの値を制御できる機能です。
今回のような、特定施設に対してのみの公開や、契約プランによる公開制御など、複数の条件を組み合わせた制御が可能です。
例えば、以下のように設定が可能です。

マルチバリアントは、上から順に条件を評価して、条件をクリアしたフラグ情報を返す仕組みになっています。
評価は、文字列比較や大なり小なりなどの基本的な比較演算子が利用できます。
上記の例のように (and (eq $serviceId "100") (eq $role "admin") ) と設定すると、サービス ID(施設 ID)が 100 で、かつロールが admin という条件になります。
この serviceId や role の値は、アプリケーション側からフラグを取得する際にヘッダーに含めることで、条件に応じたフラグ情報を取得できます。
curl http://localhost:2772/applications/helloEcs/environments/Production/configurations/conf?flag="mulchVariantFlags" \
-H "Context: serviceId=100" \
-H "Context: role=admin"
{
"enabled": true
}
このマルチバリアントに設定した条件を随時 ON にすることで、条件に応じた段階的なロールアウトが可能になります。
AppConfig 利用した場合の公開戦略
さて、ここまでを踏まえた上で、今回の公開要件を満たすための、AppConfig の設定を考えます。
マルチバリアントを以下のように設定し、これらのバリアントを上から、順に ON にしていくことで、段階的なロールアウトと複雑な公開条件を満たすことができると考えました。
| バリアント | 条件 | 具体的な評価式 |
|---|---|---|
| 開発者ユーザー | サービス ID が XXXXXX or YYYYYY | in $serviceId ["XXXXXX", "YYYYYY"] |
| 段階リリース第一弾 | サービス ID が段階リリース対象の ID に含まれる | (and (in $serviceId ["123", "456"]) |
| 段階リリース第二弾 | 第一弾と要領は一緒で、指定する ID が違うだけ | - |
| 段階リリース第三弾 | 第一弾と要領は一緒で、指定する ID が違うだけ | - |
| 一般公開 | 上記の条件に該当しない施設 | (and (not (eq $contractPlan "特定プラン")) (not (eq $facilityType "特定施設"))) |
| 自治体ホワイトリスト公開 | 自治体かつ指定された特定の施設かつホワイトリストに含まれる | (and (eq $municipalityType "自治体") (eq $facilityType "特定施設") (in $serviceId ["123", "456"])) |
| 販売パートナーホワイトリスト公開 | 販売パートナー経由かつ指定された特定の施設かつホワイトリストに含まれる | (and (eq $contractType "販売パートナー") (eq $facilityType "特定施設") (in $serviceId ["789", "101112"])) |
AppConfig 利用の課題
実際にバリアントの設定を検討した結果、以下の課題が見つかりました。
- 公開条件の確からしさの担保
- ホワイトリストの運用方法
公開条件の確からしさの担保
前述にもある通り、AppConfig のマルチバリアントは上から順に評価され、最初に条件を満たした結果が返されます。
そのため、公開条件の確からしさを担保するためには、バリアントの順番や条件式をテストケースとして網羅的に確認する必要があります。
しかし、AppConfig 自体にはテスト機能がないため、これらの確認は手動で行う必要があります。
また、(今回においては変更するケースは想定していないものの)将来的に公開条件が変更された場合にも、同様の確認作業が必要となり、運用コストが増大する懸念がありました。
チーム内でもここまで条件を記載するのであれば、テストコードで担保したいという意見も多くありました。
ホワイトリストの運用方法
公開要件にもある通り、自治体の施設と販売パートナー経由の契約の施設に関しては、一般公開後もホワイトリストを更新します。
そのため、 公開のたびに AppConfig の条件式を更新する必要があります。
AppConfig の設定変更は AWS コンソールや CLI、API 経由で行うことができますが、更新頻度が高いことや、オペレーションミスのリスクを考慮すると、何らかの方法で自動化する必要がありました。
イメージ的には、公開対象の ID だけを管理する仕組みを用意し、そこから AppConfig の設定を更新するような仕組みまで構築すれば、運用に耐えうると考えましたが、その仕組みを構築するためのコストも無視できないものでした。
データベースで制御する方法
次に、データベースで制御する方法を検討しました。
これは、各施設の公開状態をデータベースのテーブルで管理し、アプリケーション側でその情報を参照して公開制御を行う方法です。
データベースを利用した場合の公開戦略
全体像は以下の通りです。

バックエンド側で施設の公開条件を判定し、必要な情報はテーブルで管理します。
ポイントは、公開戦略テーブルに、現在の公開方法を管理し、バックエンド側で公開戦略に応じた公開条件の判定を行うようにしたことです。
段階リリースフェーズでは以下のようになります。

公開戦略テーブルの値をSTAGE_RELEASEに設定し、段階リリース対象施設テーブルで公開対象施設を管理します。
段階リリースフェーズでは、段階リリース対象施設テーブルのみ参照するため、一般公開時に利用するホワイトリスト(自治体・販売パートナーの公開対象施設テーブル)のテーブルを事前に用意しておくことができます。
一般公開フェーズでは以下のようになります。

公開戦略テーブルの値をGENERAL_RELEASEに設定し、ホワイトリストテーブルで自治体と販売パートナーのホワイトリストを管理します。
先述の通り、段階リリース時にホワイトリストテーブルのデータを事前に登録しておくことで、一般公開時は公開戦略テーブルの値を切り替えるだけで、一般公開が可能となります。
一般公開後は、順次ホワイトリストのテーブルを更新していくことで、特定施設への手動公開の運用も実現できます。
具体的な実装方法
公開条件の制御は、バックエンド側(Kotlin)で実装しました。
公開制御にも記載しましたが、実際には自治体や契約種別の判定が複数条件の組み合わせのため、もう少しコードは複雑なのですが、本記事では簡略化した形で記載しています。
まず、公開ロジックをもつ、ReleaseStrategy インターフェースを定義しました。
そして公開戦略に応じた具体的な公開ロジックを実装します。
interface ReleaseStrategy { /** * リリース戦略に基づいて、公開状態を判定する。 * @param isInStageRelease 段階リリース対象に該当しているかどうか * @param isSpecialContract 特定の契約プランかどうか * @param isSpecialFacility 特定の施設かどうか * @param isSpecifiedMunicipalityFacility 自治体かつ指定された特定の施設かどうか * @param isInMunicipalityWhitelist 自治体ホワイトリストに含まれているかどうか * @param isSpecifiedResellerFacility 販売パートナー経由かつ指定された特定の施設かどうか * @param isInResellerWhitelist 販売パートナーホワイトリストに含まれているかどうか */ fun isPublic( isInStageRelease: Boolean, isSpecialContract: Boolean, isSpecialFacility: Boolean, isSpecifiedMunicipalityFacility: Boolean, isInMunicipalityWhitelist: Boolean, isSpecifiedResellerFacility: Boolean, isInResellerWhitelist: Boolean, ): Boolean } /** * 段階リリース戦略 - 段階リリース対象のみ公開 */ data object StageReleaseStrategy : ReleaseStrategy { override fun isPublic( isInStageRelease: Boolean, isSpecialContract: Boolean, isSpecialFacility: Boolean, isSpecifiedMunicipalityFacility: Boolean, isInMunicipalityWhitelist: Boolean, isSpecifiedResellerFacility: Boolean, isInResellerWhitelist: Boolean, ): Boolean = isInStageRelease } /** * 一般公開戦略 - ホワイトリストによる公開制御 */ data object GeneralReleaseStrategy : ReleaseStrategy { override fun isPublic( isInStageRelease: Boolean, isSpecialContract: Boolean, isSpecialFacility: Boolean, isSpecifiedMunicipalityFacility: Boolean, isInMunicipalityWhitelist: Boolean, isSpecifiedResellerFacility: Boolean, isInResellerWhitelist: Boolean, ): Boolean { // 特定の契約プランは非公開 if (isSpecialContract) return false // 特定の施設は非公開 if (isSpecialFacility) return false return when { isSpecifiedMunicipalityFacility -> isInMunicipalityWhitelist // 自治体かつ指定された特定の施設の場合、ホワイトリストに含まれているかで判定 isSpecifiedResellerFacility -> isInResellerWhitelist // 販売パートナー経由かつ指定された特定の施設の場合、ホワイトリストに含まれているかで判定 else -> true // 上記の条件に該当しない施設は全て公開 } } }
このインターフェースは以下のように使用します。
ポイントは、公開戦略名(ReleaseStrategyName)に応じて、適切な公開戦略インスタンスに変換する adapt 関数を定義している点です。
こうすることで、公開戦略名を更新するだけで、公開ロジックを切り替えられるようにしています。
// 使用例 enum class ReleaseStrategyName { STAGE_RELEASE, // 段階リリース GENERAL_RELEASE, // 一般公開 } class ReleaseController { fun checkPublicStatus(facilityContext: FacilityContext): Boolean { val activeStrategy = getCurrentStrategy() // DBから現在の戦略を取得 return activeStrategy.adapt().isPublic( isInStageRelease = facilityContext.isInStageRelease, isSpecialContract = facilityContext.isSpecialContract, isSpecialFacility = facilityContext.isSpecialFacility, isSpecifiedMunicipalityFacility = facilityContext.isSpecifiedMunicipalityFacility, isInMunicipalityWhitelist = facilityContext.isInMunicipalityWhitelist, isSpecifiedResellerFacility = facilityContext.isSpecifiedResellerFacility, isInResellerWhitelist = facilityContext.isInResellerWhitelist ) } private fun ReleaseStrategyName?.adapt() = when (this) { ReleaseStrategyName.STAGE_RELEASE -> StageReleaseStrategy ReleaseStrategyName.GENERAL_RELEASE -> GeneralReleaseStrategy null -> GeneralReleaseStrategy // デフォルト戦略 } }
データベース制御を採用した理由
AppConfig の課題を踏まえた上で、最終的にデータベースでの制御を採用しました。
AppConfig で上がった課題が解消できたのが主な理由ですが、以下の通りです。
- 公開条件の確からしさの担保
- 公開条件のロジックをドメインで表現することで、ユニットテストで条件の確からしさを担保できた
- IT, E2E についても従来の方法(DB にテストデータを入れたりする等)で実装できたため、テストコードの整備も容易だった
- ホワイトリストの運用方法
- DB のレコードを更新するための、GitHub Actions を用意することで、最低限の自動化が実現できた
- GitHub Action からデータベースの値を更新する仕組みは、他のサービスで運用実績もあったため、実装コストも低く抑えられた
リリースの振り返り
実際に今回の方法でリリースを実施しました。
公開戦略名の変更においても、GitHub Actions を用意したため、作業は数分で完了することができました!
万が一、一般公開後に不具合が発覚した場合でも、公開戦略名をSTAGE_RELEASEに戻すだけで、段階リリースフェーズに戻せるため、安心してリリースを行うことができたことも大きなメリットでした。
リリース後は、ホワイトリストの更新も GitHub Actions を用いて運用していますが、特に問題なく運用できています。
ゆくゆくは、ホワイトリストの更新も Web 管理画面から行えるようにし、開発者以外でも運用できるようになれば良いなと考えています。
さいごに
今回は、複雑な公開制御において、AppConfig と DB でのロールアウト制御を検討した結果、DB での実装を選択した理由について解説しました。
複雑な公開条件や運用コストを考慮した上で、最適な方法を選択することが重要だと改めて感じました。
段階リリースを検討している方や、フィーチャーフラグでは表現しきれない複雑な公開条件に悩んでいる方の参考になれば幸いです。