Perl Hackers Hub

第78回Perl Webアプリケーションのリプレイスで大事なこと ~Stranglerパターンで段階的に移行する(1)

本連載では第一線のPerlハッカーが回替わりで執筆していきます。今回のハッカーは須藤将史さんで、テーマは「Perl Webアプリケーションのリプレイス」です。

現役のPerl Webアプリケーションをどう改善するか

新規事業をPerlで開発する事例は減りましたが、Perlで開発されて現在も稼働中のサービスはあります。それらはクライアントとサーバが密結合したモノリスなWebアプリケーションの場合も多いでしょう。その場合、開発効率改善のために「クライアントとサーバを疎結合にしてAPI駆動な開発へリプレイスしたい」と考える方もいるはずです。

しかし、リプレイスはほかのタスクと比較すると優先度が低くなりがちです。なぜなら、リプレイスは長い時間と人材や資金が必要だからです。さらにリプレイスは、ユーザーのニーズよりも、サービスを運営する組織の課題解決を一時的に優先するとも言えます。ユーザーのニーズと利益を最優先に考えると、リプレイスの優先度は高くありません。一方で、技術の進歩と変化するユーザーのニーズに合わせてサービスが成長するために、リプレイスが必要になるのも事実です。

これらを踏まえたとき、Perl Webアプリケーションのリプレイスで大事なことは2つです。一つは、Martin Fowlerが提唱したStranglerパターン[1]を使うこと、もう一つは、便利なCPANモジュールを導入してモダンな書き方へ段階的に移行することです。

本稿ではまず、先人たちが提唱するリプレイスの方法から、リプレイスは、既存のコードを活用して段階的に進めながら、リプレイスの中断や再開も行えることが重要だと述べます。次に、Perlの後方互換性とStranglerパターンに着目して、既存のコードを活かしつつ新規のコードへ段階的に移行する方法を書きます。最後に、CPANモジュールを使ってモダンな書き方ができるAPIの実装を紹介します。

リプレイスの心得 ─⁠─ 先人たちの知恵を借りる

本稿のリプレイスはStranglerパターンや『レガシーソフトウェア改善ガイド』[2]が紹介している「インクリメンタルな書き直し」を参考にしています。これらの先人たちの手法の共通点から、現役のPerl Webアプリケーションをどう改善できるかを説明します。

既存のコードは捨てずに、活かせるものは活かす

みなさんのサービスが多くのユーザーに利用されているならば、既存のコードを捨てずに活かせるものは活かすのが賢明です。なぜなら、稼働中のコードは多くのユーザーに利用され、テストされ、多くのバグが修正されているからです[3]

一方で、リプレイスをきっかけに、開発者も多くSDKSoftware Development Kitのサポートも手厚い言語へ変更したくなります。開発言語の変更はリプレイスではなくリライトになり、既存のしくみやコードを活かすのが難しくなります。既存のコードを別の開発言語へ書き換えないと解決できない課題がない限り、既存のコードはできるだけ活かすことを考えるべきです。

既存のコードを活かすなら、Perlの後方互換性は有用です。Perl 5の後方互換性は比較的よく保たれており、コードをあまり書き換えずにPerlのバージョンアップやモダンなCPANモジュールを利用できます。この後方互換性を利用すれば、既存のコードを捨てずに活かせるものは活かす選択をとれます。

リプレイスは段階的に進めて、中断したり再開してもよい

稼働中のサービスをリプレイスするなら、仕様変更は最小限にとどめます。変更範囲を絞り、機能単位で段階的にリプレイスを進めます。これらはリプレイスの技術的なハードルを下げ、失敗のリスクをできるだけ小さくするために重要です。

たとえば、画面のURLパスをロードバランサのルーティングで判定して、向きを移行先のアプリケーションへ段階的に切り替える方法があります[4]。まずは、機能が少なく影響範囲も画面単位で閉じるものを対象にするとよいです。

そして、リプレイスは中断や再開ができる状態にすべきです。リプレイスのみに開発チームの全リソースを当てられる組織はまれです。多くの場合、ユーザーからの要望や売上を改善するタスクが優先されます。中断や再開ができれば、優先されるタスクが片付いたあとにリプレイスを再開できます。さらに、失敗する可能性を考慮してロールバックも可能にすべきです。リプレイスしたコードをもとに戻せる状態を一定期間は保ちましょう。これらの対策は長いリプレイスを安全に進めるために必ず検討すべきです[5]

Stranglerパターンから学ぶリプレイスのプロセス

ここまでに述べたリプレイスの心得を具体化したのが、冒頭で紹介したMartin FowlerのStranglerパターンです。このパターンは、新しいサービスを導入しながら既存のシステムを包みつつ、外側から内側に向けて新しいシステムへと段階的に置き換えるアプローチをとります。Stranglerとは着生のつる植物のことです。つるは高木の周囲に癒着して最終的に木を絞め殺すことから、新しいコードをつる植物に、既存のシステムを木になぞらえて名付けられました。

このパターンの初期段階では、既存のコードが新しいシステムの上で安定稼働する状態を目指します。既存のコードを新しいコードで包めば、既存のコードを変更せずに影響範囲を限定できます。これで、リプレイスの初期コストと変更で発生するリスクの低下が見込めます。さらに、変更範囲が小さいため、早期のデプロイやリリースが可能となり、本番稼働で明らかになる課題やユーザーからのフィードバックを早期に受け取れます。

このサイクルを長期的に回しながら、新しいコードで既存のコードの課題を解決していきます。

リプレイスの設計 ─⁠─ 既存のコードをモダンなアーキテクチャで包む

前節では、先人たちの手法の共通点からリプレイスの心得を学びました。本節では、Stranglerパターンを参考にモノリスなPerl WebアプリケーションをAPI駆動な開発へリプレイスする設計を説明します。

本稿はPerlの連載であるため、クライアントの実装は省き、Perlで実装するREST APIサーバを中心にサンプルコードを使って説明します。サンプルコードはGitHubに公開しています。利用するPerlのバージョンは5.36.0です。各種CPANモジュールのバージョンは執筆時点での最新版を利用しています。詳細は、サンプルコードのcpanfileを参照してください。

既存のコードと新規のコードをlibで分割する

今回は説明を簡略化して、既存のコードはデータベースを直接参照するコードのみにします。まずは、既存のサービスからそのままコードを切り出します。移行前がMonolithで移行後をAPIとした場合、ディレクトリ構成は以下になります。

.
├── lib
│ ├── API # 移行後の新規コード
│ │ ├── App.pm
│ │ └── Tester
│ │ ├── Controller.pm
│ │ ├── Response.pm
│ │ └── Type.pm
│ └── Monolith # 移行前の既存コード
│ ├── DBIWrapper.pm
│ └── Repository
│ └── Tester.pm
└── t
└── controller
└── tester.t # 移行後のテストコード

サンプルコードでは、MySQLにあるtesterテーブルからidでテスターを取得するREST APIを実装しています。そして、そのテストコードをt/controller/tester.tに配置しています。

<続きの(2)こちら。>

おすすめ記事

記事・ニュース一覧