この記事は Qiita Tech Festa 2026 の投稿記事です。
【連載】レガシーWebシステムを改修する技術:Feature Toggle, シャドーイング, リファクタリング
- 第1回:環境構築とFeature Toggle(フラグ管理)編(本記事)
- 第2回:typing.Protocolとシャドーイング(ダークローンチ)編
- 第3回:仕様化テスト・TDD実践と足場のクリーンアップ編
- 第4回:LLM Agentを活用した高速・安全なリファクタリング実践編
はじめに
Qiita Tech Festa 2026 の投稿にさいして、いい機会なので少し古い手法ではあるのですが、レガシーWebシステムで ダウンタイムやデグレリスクを最小限に抑えつつ新旧ロジックを切り替える手法 をアウトプットしようと思います。割とボリュームがあるので4回に分けての記事にしようと思います。
対象はアプリケーション固有の価値となる場所で特にテストを厚くする重要性の高い、コアロジックを取り扱う層(アーキテクチャデザインパターンによって呼称が違いますが、ユースケースやサービスなどと呼ばれる層になると思います)とします。
第1回となる本記事では、リファクタリングを安全に行うための「土台」となるローカル開発環境の構築と、Feature Toggle を管理するサーバーの実装について解説します。
なぜアプリケーションで Feature Toggle を実装するのか?
モダンな開発環境であれば、LaunchDarkly などの Feature Toggle SaaSや、Istio などのサービスメッシュによるインフラ層でのトラフィックシャドーイングを利用するのが定石です。
しかし、レガシーシステム現場では
- 「新しい外部SaaSの導入ハードルが高い」
- 「インフラ構成を気軽に変更できない」
といった制約が多々あります。そのため本連載では、アプリケーションコードとシンプルなDocker環境という手元の技術だけ で完結させるアプローチを採用しています。
実際にはKVSサービスを利用するなどした方がよいとは思いますが、ミニマムなPoCとしても解説や環境整備のしやすさを優先して今回はアプリケーションサーバとして実装することにします。
概要・前提条件
技術スタック
- Python 3.11以上 ※実際には執筆時点で EOL手前 の 3.10 でも可能なはずです
- FastAPI
- Docker Compose
- pytest
全体アーキテクチャとディレクトリ構成
このハンズオン環境では、クライアントからのリクエストを処理する web_server と、フラグの状態を管理・配信する toggle_server の2つのコンテナを起動します。これにより、アプリケーションコードを再デプロイすることなく、リアルタイムに新旧ロジックを制御・検証できます。
├── docker-compose.yml
├── toggle_server/
│ ├── Dockerfile
│ └── main.py
└── web_server/
├── Dockerfile
├── requirements.txt
├── app/
│ ├── __init__.py
│ ├── services.py # 既存コード・Protocol・新コード・Wrapper
│ └── main.py # Webサーバーのエントリポイント
└── tests/
├── __init__.py
├── test_characterization.py # 1. 既存コードへの仕様化テスト
└── test_new_logic.py # 2. 新コードのRed-Green TDD
本題・実装:環境定義 (Docker Compose)
まずは2つのサーバーをオーケストレーションする docker-compose.yml と、各コンテナの Dockerfile を定義します。
services:
web:
build: ./web_server
ports:
- "8000:8000"
environment:
- TOGGLE_SERVER_URL=http://toggle:8001/toggles/use-new-service
volumes:
- ./web_server:/app
depends_on:
- toggle
toggle:
build: ./toggle_server
ports:
- "8001:8001"
volumes:
- ./toggle_server:/app
FROM python:3.11-slim
WORKDIR /app
COPY requirements.txt .
# 依存関係がインストールごとにブレるようなことがない限り、レガシーシステムに多い
# pip でのインストールのままとします
# ※ uv などへの移管は別トピックとして今回は取り扱わないものとします
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000", "--reload"]
※ web_server/requirements.txt には fastapi, uvicorn, httpx, pytest を記述します。
軽量トグルサーバーの実装
Feature Toggleのサーバー側は、特定のフラグの有効・無効をJSONで返すシンプルなAPIです。開発中や本番運用中にここの状態を書き換えることで、動的にWebサーバーの挙動を制御します。
今回は以下の2つのトグルを管理するものとします。
-
enabled :
trueでユーザには新コードの実行結果を返却。falseでユーザには既存コードの実行結果を返却 (ディフォルトは既存コード参照) -
shadow_mode :
trueで新と既存の結果を比較。falseでenabledで指定されたコードのみ参照
これを使って以下の段階的な切り替えを行っていく想定です。
- 既存の挙動 (
false,false) - シャドーイングを導入 (
false,true) - 新コードで検証 (
true,true) - 新コードでの挙動に切り替え (
true,false)
トグルサーバのコード例は以下になります。
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()
# 擬似的なインメモリDB(本番環境ではRedisや専用SaaS等を使用)
toggles = {
"use-new-service": {
"enabled": False, # 初期値は既存コード優先 (デフォルト)
"shadow_mode": True # 裏側で新コードも走らせて比較するモード
}
}
class ToggleResponse(BaseModel):
enabled: bool
shadow_mode: bool
# 現在のフラグの状態を返す
# 例) GET /toggles/use-new-service
# {"enabled":false,"shadow_mode":true}
@app.get("/toggles/{name}", response_model=ToggleResponse)
def get_toggle(name: str):
return toggles.get(name, {"enabled": False, "shadow_mode": False})
# enabled=True に変更する
# 例) POST /toggles/use-new-service/enable
@app.post("/toggles/{name}/enable")
def enable_toggle(name: str):
if name in toggles:
toggles[name]["enabled"] = True
return toggles.get(name)
# 以降、enabled, shadow_mode を切り替えるエンドポイントを作るが略
if __name__ == "__main__":
import uvicorn
uvicorn.run(app, host="0.0.0.0", port=8001)
おわりに
第1回では、リファクタリングを支えるローカル開発環境として、Webサーバーとトグルサーバーの連携環境を構築しました。
次回の第2回では、この土台の上で typing.Protocol を活用した既存コードの抽象化と、シャドーイング(ダークローンチ)による安全な新旧切り替えロジックの実装方法を記載していきます。