10年モノのレガシーPHPアプリケーションを移植しきるまでの泥臭くも長い軌跡 / legacy-php-app-migration
by
toshimaru
Link
Embed
Share
Beginning
This slide
Copy link URL
Copy link URL
Copy iframe embed code
Copy iframe embed code
Copy javascript embed code
Copy javascript embed code
Share
Tweet
Share
Tweet
Slide 1
Slide 1 text
10 年モノのレガシーPHP アプリ ケーションを移植しきるまで の泥臭くも長い軌跡 2024/03/08 PHPerKaigi 2024 Toshimaru Enomoto
Slide 2
Slide 2 text
自己紹介 • としまる( : @toshimaru_e ) • メドピア株式会社 基盤開発グループ エンジニア • グループ・ミッション:「健全なサービス基盤を提供・維持し、medpeer.jp の円滑な事業成長に貢献する。」 • 技術的負債解消マンやっています • 元・PHPer → 現・Rubyist • Ruby も好き、PHP も好き
Slide 3
Slide 3 text
話すこと • 10 年モノのレガシーなPHP アプリケーションをRaills アプリケーショ ンに移植した話をします • 基本的にカッコいい感じの話でなく泥臭めな話が多いと思います
Slide 4
Slide 4 text
話さないこと • 最新のPHP 事情 • 俺が考える最強のイケてるアーキテクチャ • 移植前・移植後のシステム詳細
Slide 5
Slide 5 text
本発表のゴール • 下記のオーディエンスに参考になる話があれば嬉しい • 今後、技術的負債の返済をしていく予定の開発者 • 現在進行系で技術的負債の返済を行っている開発者 • 将来技術的負債を返すことになりそうな開発者 • 本発表を技術的負債返済ストーリーの一事例として、話のネタにして もらえれば
Slide 6
Slide 6 text
CONTENTS WHY なぜ移植するか? HOW RESULT FUTURE どう移植するか? 結果どうなったか? 課題と今後どうするか?
Slide 7
Slide 7 text
How Legacy? 〜そのApp, どれくらいレガシー?〜
Slide 8
Slide 8 text
どれくらいレガシー? ✓ オレオレ・フレームワーク(自 作の独自フレームワーク) ✓ とっくにEOL なPHP バージョン ✓ 標準サポート終了したEC2 サー バー ✓ PEAR/PECL なライブラリ管理 ✓ 自動化されていないテスト・デ プロイ ✓ 担当プロダクトオーナー不在 ✓ 担当エンジニア不在 ✓ 仕様に関するドキュメントはほ ぼ無し(ソースコードが仕様 書) ✓ 無駄な処理が多くパフォーマン スが悪い
Slide 9
Slide 9 text
登場人物・システム • Legacy App: 移植対象となるPHP アプリケーション(主人公)。 • オレオレ・PHP フレームワーク製 • 本発表で「レガシー化する」といったときには、「負債化がひどく進行し、著し くメンテナンスしにくい状態」を指します • New App: 移植先となるRails アプリケーション。 • Ruby on Rails 製(現在の社内標準技術) • New: Legacy App と比べて相対的に新しいという意味 • 移植プロジェクトチーム: Legacy App→New App の移植を担当するチーム。
Slide 10
Slide 10 text
Legacy App New App Reverse Proxy システム・アーキテクチャ
Slide 11
Slide 11 text
WHY? 〜なぜ移植する?〜
Slide 12
Slide 12 text
2007 年〜 医師専用コミュニティサイト「Next Doctors (現 MedPeer )」の運用を開始 2009 年 2014 年 「Next Doctors 」を「MedPeer 」に改称 東証マザーズ市場上場 2015 年 2016 年 共通サービスをGo で一部マイクロサービス化 PHP から Rails への移植作業開始 2017 年 2022 年 社内標準技術はRails で着地 本格的なPHP の移植プロジェクトがスタート 黎明期 発展期 迷走期 安定期 MedPeer の沿革と言語選定の歴史
Slide 13
Slide 13 text
なぜLegacy App は放置された? • 過去にLegacy App の移植プロジェクトは断続的に立ち上がっていた • それらのどれもが結果として局所的な移植として終わってきた • 工数・予算・人的資源の制限で移植しきれなかった
Slide 14
Slide 14 text
なぜ移植するか? • Legacy App のPHP バージョンがEOL • 近年のセキュリティ・インシデント増加を背景として「EOL を迎えたソ フトウェアは基本使わないように!」というお達しが出る (2021 年) • Legacy App のメンテナビリティが厳しい • 社内からPHP 専任のエンジニアは消失(退職 or Ruby エンジニアへ転身) • 臭いものに蓋をし続けて、パンドラの箱状態に
Slide 15
Slide 15 text
なぜ移植するか? • 組織の規模も大きくなり、組織として技術的負債返済にリソース投下 できる状態になった • 2022 年、本格的な Legacy App 移植プロジェクトチーム(3-5 名)が 発足、本格的な移植作業がスタート
Slide 16
Slide 16 text
HOW? 〜どう移植する?〜
Slide 17
Slide 17 text
「どう移植するか?」 CONTENTS 4つの負債解消アプローチから考える Legacy App 討伐戦略 “ソフトウェア開発の三本柱” で足回りを整える 3つの移植方針から考える Legacy App 移植戦略 Input/Output で考える実装方針 フィーチャーフラグを用いたスムーズな移植 01 02 03 04 05
Slide 18
Slide 18 text
4つの負債解消アプローチ から考えるLegacy App 討伐戦略 01
Slide 19
Slide 19 text
4つの負債解消アプローチ 家で例えると… 説明 リファクタリング リノベーション Legacy App をコツコツとリファクタリング マイグレーション お引越し Legacy App → New App へマイグレーション リプレイス 作り直し スクラップ& ビルド Legacy App を全廃棄、New App をフルスクラッチ 削除 解体 取り壊し Legacy App を削除
Slide 20
Slide 20 text
๏ ボトムアップで少しずつ安全 に進められる ① リファクタリング(リノベ戦略) PROS CONS ๏ チマチマと直すので、全てを キレイにするには膨大な時間 がかかる レガシー化する前の健全なシステムであれば有効なアプローチ (※レガシー化した後では「手遅れ」なケースが多い)
Slide 21
Slide 21 text
๏ 機能単位・サービス単位・コ ンポーネント単位で安全に進 められる ๏ 優先度の高い一部分だけを移 植対象にすることが可能 ② マイグレーション(お引越し戦略) PROS CONS ๏ 移植しやすい単位を適切に切る必要があ る ๏ 新旧システムの互換性を考慮する必要アリ ๏ 新旧システムの並行運用期間が必要 ๏ 優先度の低い機能は放置され「塩漬け」 になるリスク 安全性・速度・柔軟性ともにバランスのとれたアプローチ
Slide 22
Slide 22 text
๏ 一撃で負債解消できる ๏ 新旧システムの並行運用期間 は短くできる ③ リプレイス(スクラップ&ビルド戦略) PROS CONS ๏ 必然的にビッグバンリリースとなりリ スク大 ๏ スコープがでかいので時間がとてもか かる ๏ トップダウンの意思決定(経営層の理 解)が必要 成功させれば一撃必殺となるハイリスク・ハイリターンなアプローチ
Slide 23
Slide 23 text
๏ 簡単!早い!(消すだけ) ๏ 安い!(工数かからない) ④ 削除(取り壊し戦略) PROS CONS ๏ 「なるほど完璧な作戦っスね―ッ 不 可能だという点に目をつぶればよぉ 〜」 ๏ 巻き込み事故に注意(必要ないと思っ て消したら実は必要だったケース) これができれば最高なアプローチ (※現実的には全て削除は不可能で、部分的な削除になることが多い)
Slide 24
Slide 24 text
4つの負債解消アプローチ まとめ 速度 安全性 工数 オススメ度 リファクタリング ✕ ◎ ◯ ✕ マイグレーション △ ◯ △ ~ ◯ ◯ リプレイス ◯ ✕ △ △ 削除 ◎ △ ~ ◯ ◎ ◯ レガシー化が進行した後ではあま りに無力! 安全に小さく始めることができる のでまずはコレがオススメ! 比較的小さいサイズの負債であれ ばワンチャン狙うのもアリ? 捨てれる負債はさっさと捨てるの が吉!
Slide 25
Slide 25 text
Legacy App 討伐戦略 • マイグレーション(お引越し戦略) • ストラングラーフィグパターンで少しずつ移植を進めた • 削除(取り壊し戦略) • 移植不要な機能・画面・仕様は削除しつつ移植 • 結果として移植対象の10-15% 程度は削除できた
Slide 26
Slide 26 text
“ソフトウェア開発の三本柱” で足回りを整える 02
Slide 27
Slide 27 text
“ ソフトウェア開発の三本柱” (@t_wada) 27
Slide 28
Slide 28 text
“ ソフトウェア開発の三本柱” で評価 バージョン管理 自動化 テスティング GitHub でソースコード管理 部分的にPHPUnit テストコードが存在 CI/CD が未整備 28
Slide 29
Slide 29 text
開発環境をコンテナ化 • まずは Dockerfile で Legacy App をコンテナ化 • 依存をコンテナ内に閉じ込める • ポータブルな環境の構築 • 開発環境構築プロセスの簡易化 テスト環境でも再利用可能なコンテナの誕生
Slide 30
Slide 30 text
テスティング・CI • GitHub Actions でコンテナ化したLegacy App をビルドし、既存のPHPUnit を実 行するところからスタート • テストケースが不十分とはいえ、CI が回り始めてステータスが になる安心 感はサイコー • 社内標準技術でE2E テスト環境を整備 (RSpec+Capybara+HeadlessChrome) • " 対レガシーコード戦の初期フェーズにおいて、E2E テストで砦を作ることは 非常に有効です" (「品質とスピードに関する16 の質問に答えてみた」 t_wada (和田 卓人))
Slide 31
Slide 31 text
CD ・デプロイ自動化 • Before (旧・デプロイ方式) • 古き良き、サーバーSSH+ デプロイスクリプト実行 • After (新・デプロイ方式) • GitHub Actions からOIDC でAWS 認証を通して、AWS Systems Manager の Run Command でデプロイスクリプトを実行 既存のデプロイスクリプト資産を活かしつつ、デプロイ自動化を達成
Slide 32
Slide 32 text
“ ソフトウェア開発の三本柱” が整った! バージョン管理 自動化 テスティング GitHub でソースコード管理 PHPUnit + E2E テスト GitHub Actions でCI/CD を整備 32
Slide 33
Slide 33 text
Let’s 移植!
Slide 34
Slide 34 text
コードフリーズ宣言 • Legacy App のコードフリーズを宣言 • これ以上技術的負債が膨らまないように入口を塞ぐ • 「変更を加えるならテストを書いてね!」という制約を課す • 例外として Legacy App の弱体化につながる変更(機能削除・コード削除)は OK とする • 上記がきちんと守られるように、移植プロジェクトチームを Legacy App リポジ トリの CODEOWNER に設定 • Legacy App の変更すべてに移植プロジェクトチームのレビューの目が入る
Slide 35
Slide 35 text
3つの移植方針から考える Legacy App 移植戦略 03
Slide 36
Slide 36 text
3つの移植方針 方針 説明 ロジックKeep移植 Legacy App のロジックを維持したまま移植。 仕様Keep移植 Legacy App の仕様を維持したまま移植。 リニューアル Legacy App をゼロからリニューアル。
Slide 37
Slide 37 text
① ロジックKeep 移植 Legacy App のロジックを維持したまま移植 • 同じ言語間・似ているフレームワーク間の移植であれば有効な手段に なりえる • 異なる言語間・フレームワーク間の移植だと無理が出る • 過去の移植プロジェクトはこの方針で移植を実施し、Rails CoC 無 視なコードが書かれ、結果として New App 側に新たな負債を生む ことに…
Slide 38
Slide 38 text
• 仕様は保ったまま、内部構造だけを書き変える • 開発者⇔プロダクトオーナー間の仕様調整作業が発生しないのは◯ • 技術的負債が解消される一方、下記の問題は残る • 前時代的なUI • 仕様負債(不要になった仕様・無駄に複雑な仕様) ② 仕様Keep 移植 Legacy App の仕様を維持したまま移植
Slide 39
Slide 39 text
• 技術的負債の解消に加えて、下記も達成できる • 前時代的なUI の刷新 • 仕様負債の解消 • どうせ移植に工数割くくらいなら、リニューアルするのがベストな選 択! ③ リニューアル Legacy App をゼロからリニューアル
Slide 40
Slide 40 text
• しかし… • 担当プロダクトオーナーが不在→リニューアル後の仕様が決まらな い・決められない • PO がいても「リニューアルするする詐欺」状態で、いつまでたって もリニューアルの話は進まない • リニューアルできるならとっくにリニューアルしてるのだわ… ③ リニューアル Legacy App をゼロからリニューアル
Slide 41
Slide 41 text
移植方針まとめ 仕様 UI ロジック おすすめ度 ロジックKeep移植 そのまま そのまま そのまま △ 仕様Keep移植 そのまま そのまま 変更 ◯ リニューアル 変更 変更 変更 ◯ 多くの場合で、コードを読み解いて 再実装したほうが結果的に速そう 仕様調整の必要がないので、開発者 のみでサクサク進められるのが◯ リニューアルできるのであれば、 さっさとリニューアルするのが吉!
Slide 42
Slide 42 text
Legacy App 移植戦略 • 仕様Keep 移植 • 仕様負債が散見されるものの、仕様変更はせず移植する • 動作確認も Legacy App ⇔ New App で動作比較をすればいいだけで簡単 • 仕様をKeep しつつも明らかに不要なコードベースは移植対象から除外 • どこからも参照されていない画面 • どこから呼ばれていない JavaScript コード • 表示上無意味な HTML/CSS コード
Slide 43
Slide 43 text
Legacy App 移植戦略 〜要望トリアージ編〜 • とはいえ移植をやっていると欲が出てくる • PO 「この機能、実はバグってて… ついでに直せない?」 • 開発者「この複雑な仕様、どう考えても不要じゃね?」 • 出てきた要望は下記のような判断基準でトリアージ • 実装工数を減らすことに繋がる要望は Accept • 実装工数が嵩むことに繋がる要望は Reject
Slide 44
Slide 44 text
Legacy App 移植戦略 〜既存仕様理解編〜 • 基本的には泥臭くソースコードを追って既存仕様を紐解いていく • 画面をいろいろイジったり、ソースコードを改変してみたりしながら既存 仕様の理解を深めていくプロセス • しかし2024 年に生きる我々にはAI という強力な銀の弾がある • レガシーコードをGitHub Copilot に放り投げて解説してもらうだけでも コードリーディングの速度・解像度がグンと上がる • ※ オレオレFW による独自コード・ロジック内に無駄な処理が多かったた め、Copilot による単純なコード変換はあまり使えなかった
Slide 45
Slide 45 text
Let’s 実装!
Slide 46
Slide 46 text
Input/Output で 考える実装方針 04
Slide 47
Slide 47 text
実装方針 • 仕様Keep 移植においては Input/Output が一致 していればOK • Input: リクエストパス, リクエストパラメータ • Output: View (HTML/Text), UI, データ in/out に着目して 仕様を徐々にビルドアップしていく App Input Output
Slide 48
Slide 48 text
実装 Step1 • まずを最小限の正常系をガッチリと固める • [in] リクエストパス ► [out] 静的HTML • 正常系のE2E テストを書いて土台とする • この時点ではDB インタラクションはなく、ダ ミーデータの出力でOK App Request Static HTML In Out
Slide 49
Slide 49 text
実装 Step2 • 次にDB との接合部をロジックに徐々に加えていく • [in] リクエストパス ► [out] 動的HTML • この過程で“ 外せない” 仕様はきちんとテストに 反映させていく • TIPS: バカ正直にロジック読むよりは、DB に流 れるクエリログからロジックをサルベージ・再構 築したほうが速かった App Request Dynamic HTML In Out
Slide 50
Slide 50 text
実装 Step3 • 仕上げに異常系をカバーしていく • [in] リクエストwith 不正なID ► [out]404 ページ • [in] リクエストwith 不正な入力値 ► [out] バリデーションエラー App Invalid Request Error In Out
Slide 51
Slide 51 text
フィーチャーフラグを 用いたスムーズな移植 05
Slide 52
Slide 52 text
フィーチャーフラグを利用して移植 • Legacy App に /feature という移植対象パスがあった場合、下記の ように Reverse Proxy を設定 • /feature → Legacy App • /feature2 → New App • /feature2 のパスは移植用フィーチャーフラグが ON の場合に表示 • リリース時は New App の /feature2 を /feature にして Reverse Proxy を切り替え
Slide 53
Slide 53 text
フィーチャーフラグを利用して移植(開発時) Legacy App New App Reverse Proxy /feature /feature2 - /feature/xxx - /feature/yyy - /feature/zzz - /feature2/xxx - /feature2/yyy - /feature2/zzz ※フィーチャー フラグでアクセス制御
Slide 54
Slide 54 text
Legacy App New App Reverse Proxy /feature - /feature/xxx - /feature/yyy - /feature/zzz - /feature/xxx - /feature/yyy - /feature/zzz ※フィーチャー フラグでアクセス制御 /feature2 → /feature ✕ nginx.conf で Proxy先スイッチ routes.rb の 設定切り替え フィーチャーフラグを利用して移植(リリース時)
Slide 55
Slide 55 text
Legacy App New App Reverse Proxy - /feature/xxx - /feature/yyy - /feature/zzz ※フィーチャー フラグでアクセス制御 /feature ✕ nginx.conf で 切り戻し フィーチャーフラグを利用して移植(ロールバック時) /feature - /feature/xxx - /feature/yyy - /feature/zzz 元通り
Slide 56
Slide 56 text
テスト • テスト担当を決めてテスト実施 • PO がいればPO 、いなければ移植対象に関連する運用チームであった りカスタマーサポートチームにテストを依頼 • あるべき仕様がロストしていたとしても、Legacy App ⇔ New App の 差分を確認することで仕様が保たれているかはテスト可能 • TIPS: 自動テスト項目をrspec --format documentation でド キュメント出力し、テスターに渡しておくと、仕様の理解促進やテスト 実施項目の削減にもつながって◯
Slide 57
Slide 57 text
実装→テスト→リリースを繰り返すことN 回…
Slide 58
Slide 58 text
移植DONE!!!
Slide 59
Slide 59 text
RESULT? 〜改善した?〜
Slide 60
Slide 60 text
機能Aの場合
Slide 61
Slide 61 text
機能Aの場合 レスポンス速度が 4〜8倍向上
Slide 62
Slide 62 text
機能Bの場合
Slide 63
Slide 63 text
機能Bの場合 レスポンス速度が 10倍向上
Slide 64
Slide 64 text
やったね!
Slide 65
Slide 65 text
スーパー高速化達成の内実 • 普通に実装しただけ(高速化のためのトリッキーな実装をしたわけで はない) • 無駄なクエリ・N+1 クエリを排除、効率的なクエリを心がける • 各処理において無駄な処理を実行しない • 移植対象のレスポンス速度は、New App で動いている他の機能のレス ポンス速度と比べると同水準のレスポンス速度 • Legacy App が単に遅すぎただけ説
Slide 66
Slide 66 text
Legacy App のコードベースを大幅に弱体化成功
Slide 67
Slide 67 text
Legacy App のコードベースを大幅に弱体化成功 100万行の コードベース削除
Slide 68
Slide 68 text
移植後の機能開発 • コードベースがメンテナンス可能になり、開発チームに引き継ぐこと ができた • 開発者: 社内標準技術でコードを読み書きできるようになった • PO: バグ修正依頼・機能要望を出すことができるようになった • 引き継いだことにより、エンハンス開発も再開された • 早速、移植時点ではレスポンシブデザイン非対応だったページが、 レスポンシブ対応されるという改善が入った
Slide 69
Slide 69 text
HAPPY END? 〜めでたし めでたし?〜
Slide 70
Slide 70 text
NO!!! 残念でした!
Slide 71
Slide 71 text
残課題 • Legacy App に依然として残り続けるPHP 管理画面… • Legacy App のデータベースは今も元気に稼働中! • Go で作られたマイクロサービスはどうする?
Slide 72
Slide 72 text
残課題 • Legacy App に依然として残り続けるPHP 管理画面… • Legacy App のデータベースは今も元気に稼働中! • Go で作られたマイクロサービスはどうする? 俺達の戦いは これからだ!
Slide 73
Slide 73 text
FUTURE 〜今後どうする?〜
Slide 74
Slide 74 text
今後 • 基本方針としてはモノリス化を目指す(モノリシックRails 戦略) • Modular Monolith にするかどうかは検討課題 • 本発表で紹介した移植により、Legacy App は管理画面だけとなり、 ユーザー影響を局所化できたので PHP Upgrade する方針で考えてい る • 最終的には上述のモノリスに統合させる方向で進めたい(時期未 定)
Slide 75
Slide 75 text
2007 年〜 医師専用コミュニティサイト「Next Doctors (現 MedPeer )」の運用を開始 2009 年 2014 年 「Next Doctors 」を「MedPeer 」に改称 東証マザーズ市場上場 2015 年 2016 年 共通サービスをGo で一部マイクロサービス化 PHP から Rails への移植作業開始 2017 年 2023 年 社内標準技術はRails で着地 エンドユーザー側のPHP の移植が完了 202X 年 20XX 年 PHP/Go をRails に統合 すべてのサービス群をRuby on Rails モノリス化 黎明期 発展期 迷走期 安定期 モノリス期 ✕ ✕ MedPeer アーキテクチャの今後
Slide 76
Slide 76 text
本発表の教訓 • <技術的負債返済プロジェクト>が必要になるまで技術的負債を放置 しない! • 技術的負債自体は健全な開発チームにあって当然 • 恒常的な負債返済サイクルが回っていないことが問題 • きちんと上層部に技術的負債の返済を理解してもらい、定常的な負債 返済活動を推進しよう • <エンジニア vs ビジネス> みたいな対立構図にしないことが大事
Slide 77
Slide 77 text
THANK YOU! ご清聴ありがとうございました。
Slide 78
Slide 78 text
その他のTIPS • 非エンジニアにも技術的負債について理解してもらうために、「技術 的負債とは何か」について話す機会をもらって、話をできたのは良 かった • 移植時の実装方針について議論が割れたときのために、実装方針の最 終意思決定者を私一人に決めたのは不毛な議論が減って良かった • Code Review が遅い問題があったが、このへんの知見は別発表にて まとめました: Faster Pull Request Reviews 〜ハイパフォーマンスチー ムへの道〜
Slide 79
Slide 79 text
その他のTIPS • 旧・移植プロジェクトは業務委託メインの開発だったが、新・移植プロ ジェクトは、正社員が中心となりチームを組成した • これにより進捗管理・品質の担保・仕様調整の面でプラスに働いた • あるコードが本当に使われていないかを確認するために GitHub の Org 横断検索を使うのが良かった • クエリ: org:{your-org} NOT is:archived {unused_code} • ※ ただし巨大なCSV ファイルなどは検索対象に入らなかったりするの で注意
Slide 80
Slide 80 text
参考資料 • メドピア公式ブログ・資料 • Golang (Go 言語)を採用して、たった二人で基盤となるAPI ゲートウェイを開発した話 (2015 年) • レガシーな独自フレームワークから脱却してRails へ徐々に移行している話 (2017 年) • メドピアの全力Rails 化の取り組み晒します! (2017 年) • ストラングラー フィグ パターン - Azure Architecture Center • 動作するきれいなコード: SeleniumConf Tokyo 2019 基調講演文字起こし+! - t-wada のブロ グ • カミナシでの技術的負債返済プロジェクトとその決断