ストラングラーフィグパターンとは？～レガシーシステムからの移行～

## はじめに

チームが運用しているシステムが、何年も前に作られたレガシーなアプリケーションだとしたらどうでしょう？ユーザーは不便を感じ、開発者は技術的負債に悩まされ、新しい機能を追加するたびに思わぬバグが発生する。とはいえ、システムを全面リプレースするにはリスクが高すぎる…。このような状況で頼りになるのがストラングラーフィグパターンです。

この記事では、ストラングラーフィグパターンを活用して、安全かつ段階的にシステムをモダン化する手法を紹介します。現場で使える具体的な技術スタックや、移行をスムーズに進めるためのテスト戦略も併せて説明していきます。

## ストラングラーフィグパターンとは？

ストラングラーフィグパターンは、名前の由来である「絞め殺しのイチジク」の成長プロセスに似たアプローチです。既存のシステムを一気にリプレースするのではなく、段階的に新しいシステムに置き換えていく方法です。移行が完了するまで、旧システムと新システムは共存し、プロキシ層を通じてユーザーリクエストを振り分けます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2527942/2f8d85a4-b070-a8ff-f518-b8c2ff78cd6d.png)


### なぜストラングラーフィグパターンなのか？

- リスク分散：全体を一度に置き換えるのではなく、部分的に移行することで、大規模なシステム障害を防げる
- ユーザーへの影響が少ない：移行中もシステムを稼働させることができ、サービス提供を止める必要がない
- 段階的な改善：システムの一部をモダン化しながら、改善効果を早期に享受できる

### パターンの実装手順

ストラングラーフィグパターンを成功させるためには、移行プロセスをしっかり計画し、適切な技術を選定することが重要です。ここでは、プロキシ層を活用した段階的な移行プロセスを説明します。

### 1. プロキシ層の導入

まず、既存システムと新しいシステムの間にプロキシ層を設けます。これには、NginxやAPI Gatewayなどの技術が使われます。このプロキシ層は、どの機能を既存システムで処理し、どの機能を新システムに委ねるかを動的に決定します。

#### 実装例：Nginxプロキシ設定

```nginx
server {
    location /legacy/ {
        proxy_pass http://old-system;
    }
    location /modern/ {
        proxy_pass http://new-system;
    }
}
```

#### 

```plantuml
@startuml
actor User
rectangle ProxyLayer {
  component "プロキシ" as Proxy
}
rectangle LegacySystem {
  component "既存機能 A" as OldA
  component "既存機能 B" as OldB
  component "既存機能 C" as OldC

}
rectangle NewSystem {
  component "新機能 A" as NewA
}
User --> Proxy
Proxy --> NewA : Aのリクエストは新システムへ
Proxy --> OldB : Bのリクエストは既存システムへ
Proxy --> OldC : Cのリクエストは既存システムへ
@enduml
```

### 2. 新しいシステムの段階的導入

次に、既存システムの一部機能を新しいシステムで実装します。例えば、ユーザー認証や検索機能などから始めることが多いです。この段階では、まだ既存システムの大部分が稼働していますが、新しい機能は新システムで処理されるように設定されます。

```plantuml
@startuml
actor User
rectangle ProxyLayer {
  component "プロキシ" as Proxy
}
rectangle LegacySystem {
  component "既存機能 A" as OldA
  component "既存機能 B" as OldB
  component "既存機能 C" as OldC

}
rectangle NewSystem {
  component "新機能 A" as NewA
  component "新機能 B" as NewB

}
User --> Proxy
Proxy --> NewA : Aのリクエストは新システムへ
Proxy --> NewB : Bのリクエストは新システムへ
Proxy --> OldC : Cのリクエストは既存システムへ
@enduml
```


### 3. テスト戦略：カナリアリリースとA/Bテスト

移行中に重要なのは、テスト戦略です。カナリアリリースやA/Bテストを使って、新しいシステムの信頼性を部分的に確認することができます。まず一部のユーザーにだけ新システムを適用し、問題がないか確認しながら徐々に拡大します。

### 4. 完全な移行

すべての機能が新システムで動作するようになったら、プロキシ層を通じてすべてのトラフィックを新しいシステムにリダイレクトします。この時点で、既存システムはリタイアし、完全な移行が完了します。


```plantuml
@startuml
actor User
rectangle ProxyLayer {
  component "プロキシ" as Proxy
}
rectangle LegacySystem {
  component "既存機能 A" as OldA
  component "既存機能 B" as OldB
  component "既存機能 C" as OldC

}
rectangle NewSystem {
  component "新機能 A" as NewA
  component "新機能 B" as NewB
  component "新機能 C" as NewC
}
User --> Proxy
Proxy --> NewA : Aのリクエストは新システムへ
Proxy --> NewB : Bのリクエストは新システムへ
Proxy --> NewC : Cのリクエストは新システムへ

note right of LegacySystem
完全移行されたので廃止
end note
@enduml
```


## 実際のユースケース例：オンラインショップの移行

ストラングラーフィグパターンにより移行を理解したところで、実際のユースケース例を元に移行方法を考えてみます。

### シナリオ

大手オンラインショップのエンジニアリングチームで、検索機能が古く、ユーザー体験を損なっているという課題に直面しています。現行システムでは自前のフルテキスト検索を使っていますが、パフォーマンスの低下が顕著です。これをElasticSearchを利用した高速な検索システムに置き換えたいという要件があります。

ただし、全システムを停止するわけにはいかないため、ストラングラーフィグパターンを使って段階的に移行していきます。

#### 技術スタック

- 現行システム
  - 言語: PHP
  - 検索エンジン: 自前のフルテキスト検索
  - データベース: MySQL
  - デプロイ環境: オンプレミスサーバー
- 新システム
  - 言語: Node.js
  - 検索エンジン: ElasticSearch
  - データベース: PostgreSQL
  - デプロイ環境: Kubernetes上のクラウド環境


### 移行プロセス

#### 1.	プロキシ層導入

まず、プロキシ層を導入して、ユーザーからの検索リクエストを既存システムに送るか、新システムに送るかを制御します。以下はNginxを使用したプロキシの設定例です。

```nginx
server {
    location /search {
        proxy_pass http://legacy-search-system;
    }
    location /new-search {
        proxy_pass http://new-elasticsearch-system;
    }
}
```

```plantuml
@startuml
actor User
rectangle ProxyLayer {
  component "プロキシ" as Proxy
}
rectangle ExistingSystem {
  component "既存検索システム" as OldSearch
}
rectangle NewSystem {
  component "ElasticSearch" as NewSearch
}
User --> Proxy : 検索リクエスト
Proxy --> OldSearch : リクエストの一部は既存システムへ
Proxy --> NewSearch : リクエストの一部は新システムへ
@enduml
```
#### 2. 新検索システムの実装とカナリアリリース

ElasticSearchを使用して新しい検索システムを実装し、まずはユーザーの10%にのみ新システムを提供するカナリアリリースを行います。

この段階で、新システムのパフォーマンスや信頼性をモニタリングし、検索速度やエラー率の改善を確認します。以下の図では、カナリアリリースのフローを示しています。

```plantuml
@startuml
actor User
rectangle "カナリアリリース" {
  rectangle NewSystem {
    component "ElasticSearch" as NewSearch
  }
  rectangle ExistingSystem {
    component "既存検索システム" as OldSearch
  }
}
User --> OldSearch : 90%のユーザー
User --> NewSearch : 10%のユーザー
@enduml
```

#### 3. A/Bテストによる評価

次に、A/Bテストを使用して、新旧システム間でパフォーマンスの比較を行います。新しい検索システムが既存システムよりもコンバージョン率や検索結果の精度で優れているかを確認します。

A/Bテストの流れは以下の通りです。

```plantuml
@startuml
actor User
rectangle ProxyLayer {
  component "A/B テストプロキシ" as Proxy
}
rectangle ExistingSystem {
  component "既存検索システム" as OldSearch
}
rectangle NewSystem {
  component "ElasticSearch" as NewSearch
}
User --> Proxy : 検索リクエスト
Proxy --> OldSearch : 一部のユーザーが既存システムへ
Proxy --> NewSearch : 一部のユーザーが新システムへ
@enduml
```

#### 4. 段階的なリプレース

新システムのパフォーマンスが安定し、すべての指標が改善されたら、段階的に全ユーザーを新しいElasticSearchベースのシステムに移行します。これにより、旧システムは完全に廃止され、新システムがメインの検索システムとして稼働します。


## まとめ

ストラングラーフィグパターンは、段階的なシステム移行を可能にする強力なアプローチです。大規模なリプレースに伴うリスクを低減し、システムが稼働し続ける中で少しずつモダンな技術に移行できます。このパターンを成功させるためには、適切なプロキシ層の設計や、テスト戦略の実行が不可欠です。また、実際のシナリオや技術スタックに基づいて計画を練ることで、よりスムーズな移行を実現できます。