ストラングラーフィグパターンは、旧システムを一度に置き換えず、入口でrequestを振り分けながら機能を一つずつ新システムへ移す方法です。
新旧を長く並べることが目的ではありません。小さく切り替え、観測し、必要なら戻し、最終的に旧機能を撤去できることが中心です。
基本の流れ

- 旧システムの前にrouterまたはfacadeを置く
- 最初はすべて旧システムへ通す
- 小さなbusiness capabilityを新システムへ実装する
- 一部trafficまたは特定routeだけ新側へ切り替える
- 結果と運用を確認し、旧側へ戻せる期間を取る
- 新側を正とした後、旧実装と移行用部品を撤去する
「画面」「DB table」「class」より、利用者に価値を返す機能単位で切り出します。たとえば商品検索は、注文確定より副作用が少なく、最初の候補にしやすい場合があります。
routerで切り替え境界を作る
routerはURL、header、利用者group、feature flagなどで新旧へ振り分けます。
GET /catalog/* -> new catalog service
POST /orders/* -> legacy application
other routes -> legacy application
route規則を一か所へ集め、変更履歴と現在値を確認できるようにします。複数のproxyやapplication codeへ分散すると、どちらが処理したか追えません。
切り替え時は次をtelemetryへ残します。
- route先(legacy/new)
- migration ruleのversion
- response statusとlatency
- request/trace ID
- fallbackまたは切り戻しの発生
認証情報や個人情報をrouting logへそのまま残しません。
最初の機能を選ぶ
最初から最重要の決済を選ぶ必要はありません。次を満たす候補を探します。
- 入出力と依存先を説明できる
- 旧システム内の変更が比較的少ない
- 成功条件を自動または手作業で比較できる
- 独立したrouting ruleを作れる
- 失敗時に旧側へ戻す意味が明確
価値がまったくない内部機能だけでは移行の学びが得にくく、複雑すぎる中心機能では境界作りに失敗しやすくなります。「小さいが本番価値がある」単位を選びます。
rollbackはroutingだけでは完了しない
新側のread-only機能なら、routeを旧側へ戻すだけで切り戻せる場合があります。書き込み機能では、新側が作ったデータを旧側が読めるかが問題です。
次のどれかがあると、単純なroute rollbackは危険です。
- 新側だけが知るschemaで書いた
- 外部へメール・決済・通知を送った
- queue eventを発行した
- 旧側と新側の両方が同じデータを更新した
切り替え前に「routeを戻した後、作成済みデータと副作用をどう扱うか」を試します。後方互換なschema、idempotency、処理済みeventの記録が必要です。
データ境界を決める
最も難しいのはcodeよりdataです。移行中の代表的な選択肢があります。
旧DBを一時的に共有する
新serviceが旧DBを読むと早く移行できますが、schema couplingが残ります。新serviceが旧tableへ自由に書き始めると、独立性が得られません。利用範囲と終了日を決めた一時策として扱います。
新側へdataを複製する
CDCやeventで新storeへ反映します。伝播遅延、順序、重複、schema変更、再同期が必要です。新側がread modelだけを持つ段階なら適用しやすい場合があります。
所有権を移す
ある機能の書き込みを新側だけへ切り替え、旧側はAPIやanti-corruption layer経由で参照します。最終形に近い一方、依存関係を洗い出してから切り替える必要があります。
同じfieldを新旧双方が自由に更新するdual writeは、片方だけ成功した時の修復が難しくなります。 必要ならoutbox、idempotent consumer、reconciliation jobなどを設計し、「常に同時成功する」と仮定しません。
shadow trafficの注意
同じread requestを新旧へ送り、responseを比較するshadow方式は検証に役立ちます。ただし、write requestをそのまま二重実行すると二重課金や二重通知が起きます。
shadow側はresponseを利用者へ返さず、timeoutやresource消費が本番側へ影響しないようにします。個人情報を別環境へ複製できるか、外部APIを呼ばないかも確認します。
比較ではJSON文字列の完全一致だけでなく、順序、時刻、生成IDなど許容差を定義します。
段階的な切り替え
一つのrouteを次の順で広げられます。
0%: shadowで比較
1%: 内部利用者だけnew
10%: 限定した利用者group
50%: error/latency/業務結果を監視
100%: newを正とする
retire: legacy routeとcodeを削除
割合は例であり、必ずこの順にする必要はありません。各段階の継続時間、進む条件、戻す条件を先に決めます。trafficが少ない機能では割合より、内部利用者や特定tenantで分ける方が観測しやすい場合があります。
完了条件を決める
Strangler移行が終わらない最大の原因は、「新側が動く」を完了とし、旧側と移行部品を残すことです。機能ごとに次を管理します。
- 新側へrouting済み
- data所有者が一つに決まった
- 旧側へのcallとDB accessが0になった
- rollback期間が終わった
- 旧code、flag、proxy rule、同期jobを削除した
- dashboard、alert、runbookを新側へ更新した
旧側への新機能追加を全面禁止できない場合もあります。重大bug修正や法令対応は必要です。新旧どちらへ変更するか、移行中の差分をどう反映するかを明示します。
採用しない条件
- requestを振り分ける安定した境界を作れない
- 新旧の共存が法令・data制約上許されない
- 短期間で停止できる小規模systemで、段階移行の基盤の方が高コスト
- 旧仕様が不明で、比較可能な成功条件もない
最後のケースでは、先に旧systemの観測、characterization test、data調査を行います。Strangler pattern自体が仕様を発見してくれるわけではありません。
よくある誤解
- microservices化が必須:新側もmodular monolithで構いません。
- proxyを置けば移行できる:data ownershipと副作用の境界が必要です。
- routeを戻せばrollback完了:新側の書き込みと外部副作用を確認します。
- 新旧の二重運用は安全:期間が長いほど運用、security、data同期の負担が増えます。
- 最後に旧systemを消せばよい:機能ごとに旧codeと移行部品を撤去します。
まとめ
Strangler Figは、入口でroutingし、小さな機能を新側へ移し、観測とrollbackを繰り返す移行patternです。成功の鍵は、route切り替えだけでなくdataの所有者、書き込みの副作用、撤去条件を機能ごとに決めることです。