はじめに
こんにちは、ZOZOTOWN開発本部アプリバックエンドブロックの髙井です。
私達のチームでは、レガシーとなっているZOZOTOWNアプリ用API(以下、レガシーAPIと呼ぶ)のリプレイスに2023年から着手しています。リプレイス対象となるレガシーAPIは規模が大きいので、フェーズで区切り、段階的にリプレイスを進めています。区切られた各フェーズは、フェーズ1、フェーズ2といった形で呼び分けており、フェーズごとにリプレイス対象とするエンドポイントを設定しています。一方で、事業案件や他マイクロサービスのリプレイスが並行して行われるため、フェーズごとにリプレイス計画を柔軟に調整してきました。
本記事ではレガシーAPIのリプレイスについて、フェーズ3までを担当者が背景と課題を踏まえつつ紹介していきます。
目次
背景
こんにちは。湯川と申します。まず私からは、本リプレイスプロジェクトの始まった背景と導入部分を説明します。
レガシーAPIは、十数年前にZOZOTOWNアプリがローンチされた際から存在しており、当初からVBScriptで実装されていました。しかし、年月が経ち、コードは複雑化し、メンテナンスコストが増大していました。さらに、VBScriptは歴史のある言語であり、新しいエンジニアの採用が難しい状況でした。
そこで、マイクロサービス化を進め、Webサイトをモダン化するリプレイスプロジェクトが社内で発足し、レガシーAPIもこのプロジェクトの一環としてリプレイスすることとなりました。リプレイスの目的は、VBScriptからの脱却を図り、現代的な言語とフレームワークを使用することです。
既にZOZOTOWNアプリの一部の画面で本番稼働しているBFF API(JavaのSpringで実装)をリプレイス先APIとして、レガシーAPIをリプレイスしていくこととなりました。
BFF APIについてはこちらの記事で紹介しているので、合わせてご覧ください。
まず、私たちが取り組んだのは、計画を立てるために各APIが直接DBへ問い合わせしている箇所やマイクロサービスを参照している箇所など、外部との接続がどこでどのくらいあるかを調査することでした。
この調査により、以下のポイントが明らかになりました。
- ロジックの複雑度の算出:どのくらいの接続があるかを把握することで、ロジックの複雑度を算出し、APIの移行難易度を把握することに役立ちました。
- 接続経路の特定:各APIがどこと接続しているかを把握することで、既存で存在しない接続経路があればシステムアーキテクチャに関わる新規構築が必要だと判断できました。
このポイントを押さえながら段階的なアプローチを取り、チーム内で完結する小規模なリプレイスから進めていく計画を立てました。
フェーズ1
こんにちは、アプリバックエンドブロックの伊藤です。ZOZOには2022年1月から中途入社し、2023年2月から5月にかけて実施されたレガシーAPIのリプレイスフェーズ1のプロジェクトに、開発者の1人としてアサインされていました。
課題
フェーズ1のプロジェクトには以下のような課題がありました。
1. リプレイス先APIの開発が初めて
リーダーはリプレイス先のAPI開発経験がありましたが、開発者としてアサインされたメンバーはJavaの開発経験が浅く、リプレイス先APIの開発が初めてでした。また、社歴も比較的浅いため、現状のキャッチアップが必要な状態からのスタートでした。
2. リプレイスの土台が整っていない
今後長期にわたるリプレイスの初フェーズということで、そもそも案件全体でリプレイスについて十分な知見がない状態でした。
そのため、以下のような観点から対象のエンドポイントを選定し、リプレイスを開始しました。
- 今後のフェーズの土台を築く上で、まずはリプレイスの実績を作る
- 未経験者でも置き換えしやすい、スコープが小さく複雑度の低いシンプルなものから取り組む
課題1への取り組み
課題1つ目の「リプレイス先APIの開発が初めて」に対しては以下のようなことに取り組み、課題の解決に役立ちました。
1. 毎日集まって開発状況を確認
Javaの開発経験の浅いメンバーにとって、困っていることや詳細なタスク状況を共有しやすい環境を作ることは、開発をスムーズに進める上でとても重要でした。
2. 事前に既存実装のシーケンス図を作成
処理の流れやエラーパターンが事前に視覚化されていたことで、実装面で役に立ったことはもちろん、他チームとのコミュニケーションもスムーズに進みました。
3. 積極的に有識者とのモブプロを行う
チームメンバーがどのようにコーディングやデバッグをしているかを学び、経験不足を補うことができました。
課題2への取り組み
課題2つ目の「リプレイスの土台が整っていない」に対しての取り組みは以下の通りです。
1. 開発チームメンバー全員での既存コードの読み合わせ
実装前に作成したシーケンス図へは起こしきれないような細かいロジックがありましたが、メンバー間で理解度を合わせることで、コードレビューの段階でバグを見つけることができました。
2. クライアントに影響がない実装方法を採用
レガシーAPIのインタフェースとURLは、あえて既存の形式を維持する方針でリプレイスを行いました。理由はクライアントであるアプリ側への影響なく、バックエンドとインフラのみの修正でリプレイスを進められるようにしたかったからです。そうすることで関わるチームは少なくなり、結果的にスピーディーな開発につながりました。
具体的に行った対応は以下の通りです。
- リプレイス先APIでは、レガシーAPIのインタフェース設計をそのまま引き継いだ
- アプリからのリクエストを受け付けるAPI Gatewayに対象エンドポイントのルーティング設定を追加した
3. 本番環境へのリリース手法としてカナリアリリースを採用
既存実装がかなり複雑な部分でバグを生んでしまい、リリース後に意図しないエラーが発生しましたが、段階的にN%リリースをしたため影響を最小限に抑えることができました。
カナリアリリースの詳細については以下のテックブログで紹介しておりますので、気になった方はご参照ください。
結果
課題が解決できたか?
対象エンドポイントのスコープは小さかったものの、上記のような取り組みのおかげで経験不足を補いながら無事にリリースができました。これらの取り組みは後に紹介されるフェーズ2以降でも活かされています。
また事前にわかっていた課題以外にも、以下のような実装以外のタスクが発生しましたが、プロジェクト内で都度対応しながら解決していきました。
- 今後続くリプレイス案件の土台を整えるために、最適なリプレイス方法を議論する必要があった
- リプレイス先APIのコーディング規約やレビュー規約が整っていない状態であった
- メンバー間の役割分担が上手く行かず、1人が案件リーダーと開発リーダーの2つの役割を担ったため作業過多になった
新しくリプレイスプロジェクトを始める際には、このような点を事前に考慮できると良さそうです。他にも上記の例に限らず、想定外のタスクが発生するかもしれないため、スケジュールにバッファを設けておくと良いかもしれません。
新たな課題
フェーズ1の中でわかったことは、レガシーAPIの実装は想像よりも難解であったということです。コードレビュー時にはチームメンバー全員でコードの読み合わせをしたものの、実装時には担当者が個人でレガシーAPIの処理を調査したために、もっと深いところで理解度に個人差が生まれていました。その結果リリース後のバグ発生につながってしまいましたが、バグを正しく修正するために再度チームメンバー全員でコードを読み合わせたことが役に立ちました。
フェーズ2
ZOZOTOWNアプリバックエンドのエレーナです。レガシーAPIリプレイスフェーズ2プロジェクトの開発者の1人として参加したので、フェーズ2について説明いたします。
課題
リプレイス先APIでは、データベース(以降DB)へ直接接続しない設計となっています。原則的に該当するマイクロサービスからしかDBにアクセスしません。一方でレガシーAPIは直接DBにリクエストしているところが多い状態で、置き換え方法が決まっていないことがリプレイスのブロッカーとなっていました。そのため、そのブロッカーを解除することがフェーズ2のメインの目的となりました。
その上で、同時並行で開発していた新規機能においても、リプレイス先APIからの直接DBリクエストが必要だと判明しました。新規機能のリリーススケジュールに影響を与えないように、DBアクセスの課題の優先度がさらに高まったため、早急に解決する必要がありました。
フェーズ2で取り組んだこと
モノリスなレガシーAPIをマイクロサービスで設計されたシステムへリプレイスする場合、理想的な置き換え先は以下になります。
- BFF層で管理すべきコードをリプレイス先APIへ置き換え
- マイクロサービスで管理すべきコードをマイクロサービスへ置き換え
しかし、一部のマイクロサービスがまだ設置できていない現状で、リプレイスを必ず新規サービスの設置から始めると、多大な工数がかかります。リソースが限られている状態でさらに柔軟な方法が必要なので、マイクロサービスができていない場合は該当するロジックを過渡期APIへ置き換えるようにしました。
過渡期APIとは、レガシーAPIのシステムを再利用した名前の通り仮のAPIとなっています。レガシーのインフラストラクチャー上にできているので、DBへ接続経路が存在しています。
過渡期APIに含む処理は重要なポイントが2つあります。
- その処理はリプレイス先APIでできないこと(例:DB直アクセス)
- 将来的にはマイクロサービスへ置き換える前提
サービス自体がまだ存在しなくても今のタイミングでBFF層とマイクロサービス層のロジックが切り離し可能で、過渡期APIへ置き換える余分なステップが無駄になりません。
そして最後に、事業案件の事情でフェーズ2をなるべく早めにリリースできるように、対象エンドポイントは1本で複雑度が低いものを選定しました。
結果
課題が解決できたか?
小さくてシンプルなエンドポイントを選んだおかげで不具合なしに短い期間で実装が完了して無事にリリースできました。リリーススコープが小さかったですが大きなリプレイスブロッカーを解除できました。
新たな課題
実は、リプレイスの大きなブロッカーがもう1つ残っています。レガシーAPIは端末やユーザー情報をセッションに保存および取得しているところがあるので、該当処理の置き換え方法を検討する必要があります。最初はその課題を含めてフェーズ2で全リプレイスブロッカーを解除する予定でしたが、フェーズ2のリリースを早める方が優先だったのでセッション課題の解決をフェーズ3へ移動しました。
フェーズ3
アプリバックエンドブロックのカイルです。2023年10月からレガシーAPIのリプレイスプロジェクトに参加しています。
課題
マイクロサービスを経由していないDBアクセスの経路ができたので、次はセッションに入っている情報へのアクセス経路を作るという課題の解決に取り組みました。レガシーAPIでは、ユーザーがログイン中でも未ログインでもシステム全体でユーザーの情報にアクセスできるようにセッションに保存しています。
セッションと言っても、過去のマイグレーションプロジェクトでサーバー上に保存していたデータをRedisに移行することがすでに完了していました。ただ、アクセスする方法はレガシーAPIのフレームワークで使っているプラグインのみで、違う言語で実装されているリプレイス先APIからアクセスする経路は存在しませんでした。
セッションデータが保存されているRedisに直接アクセスしてもよかったのですが、特定のデータ形式や保存先に依存したくないと考えていました。また、セッション情報は複数のシステムからアクセスする必要があるのでアクセス方法を統一する要望もありました。
レガシーAPIではセッションから取得した情報を使っているエンドポイントが多く、アクセス方法の課題が今後のリプレイスのブロッカーとなっていました。
フェーズ3で取り組んだこと
セッション情報のアクセスを統一するため、マイグレーションプロジェクトを担当しているチームでまずセッション基盤を開発することにしました。フェーズ3ではリプレイス先APIからセッション基盤への経路を追加してセッション情報を取得できるようにしました。
また、フェーズ3へ入る前にレガシーAPIが動いていたオンプレサーバーの縮退スケジュールが決まりました。縮退スケジュールに間に合うようにサーバー負荷が高いエンドポイントからリプレイスを行うことを決めました。
そこで、まずはレガシーAPIの全エンドポイントで、レガシーAPI全体のリクエスト数に対する各エンドポイントのリクエスト数の割合を算出しました。そして、この割合が高いものをサーバー負荷が高い(リプレイスの優先度が高い)エンドポイントとして優先順位を付けました。
そのため、セッションの取得が必要となり、かつサーバー負荷が高いエンドポイントとして、商品詳細画面の下部に表示されるレコメンド商品を返すAPIをフェーズ3の対象としました。
フェーズ3の進め方についても新しく実施してみたことがあります。フェーズ1の課題だったレガシーAPIの理解を改善するために、案件のキックオフ後に開発メンバー全員でコードを読みながら既存のレガシーAPIの仕様を理解したうえで開発に入りました。
結果
課題が解決できたか?
対象エンドポイントで必要となるセッション情報を新たにセッション基盤経由で取得できるようになり、今までなかった経路をひとつ実現できました。そのおかげで今後リプレイスするエンドポイントでもセッション情報の参照が必要な時にこの経路を使って実装できるようになりました。
そして事前に既存処理をすり合わせて設計を決めることによって、よりスムーズに開発を進めることができました。
新たな課題
今回できたのはセッション情報の参照のみですが、他のエンドポイントでセッション生成や更新、セッションクリアなどの操作が必要になってきます。セッション基盤ではそのようなケースをどう扱うかはまだ検討中の段階で、これから確定して実装する必要があります。
そして開発プロセスにおいても、全体の流れが安定してきた一方で新たな課題も見えてきました。特にリリースサイクルをもっと柔軟にしないといけないと感じました。今まではひとつのフェーズの対象となっているエンドポイントを全て開発して、テストとリリースをまとめて行っていましたが、フェーズが大きくなるとコードの量が膨大になって開発しづらくなります。エンドポイントごとのリリースサイクルを導入できたらコード管理やリリーススケジュールが調整しやすくなる想定です。
フェーズ3でも実際に開発の途中でスコープが変更されて、同じブランチで管理していた複数エンドポイントのコードを分ける作業が必要となりました。今後のフェーズではリリースサイクルをもっと細かくすることで開発を楽にしたいと考えています。
まとめ
本記事では、これまでのレガシーAPIのリプレイスで取り組んだ課題とその解決方法をフェーズごとに紹介しました。これまでのリプレイスによって、チーム内のJava開発経験を蓄え、各マイクロサービスへの接続経路を作る事ができました。まだリプレイスは道半ばです。今後はこれまでのリプレイスで蓄えた開発経験や整えた接続経路を基に、残りのエンドポイントのリプレイスも進めていきます。本リプレイスでのリプレイス計画の立て方、進め方が、今後APIのリプレイスを検討している方の参考になれば幸いです。
ZOZOでは、一緒にサービスを作り上げてくれる方を募集中です。ご興味のある方は、以下のリンクからぜひご応募ください。