- プロダクトマネージャー/企画
- サーバーサイド
- データアナリスト
- Other occupations (31)
- Development
- Business
- Other
この記事は2024年12月3日にエンジニアブログに掲載した内容を転載しております。
こんにちは。日本最大級のフードデリバリーサービスDemaecan(出前館、以下出前館)のプロダクトを担当しているキム・ヨンジェと申します。 いつの間にかプロダクトを刷新してから2年半となりつつあります。今回の記事は、レガシーを解消しながらサービスを革新していく出前館の旅の記録を残すという意味で書きました。
レガシーの定義は様々です。単純に古いコードを意味する場合もあれば、新しいサービスの実装に制約となる既存の構造を指す場合もあります。 しかし、辞書に載っている「遺産」というポジティブな意味のように、レガシーはこれまでのサービスを存続させるための基盤でもあります。
レガシーに関する面白い表現もたくさんあります。
- 昨日私が書いたコードはレガシーである。
- ユーザーが一人でもいるシステムはレガシーである。その一人があなた。
- レガシーを直すより、作り直した方が早い。
上記のような表現が数え切れないほど、エンジニアはレガシーのために笑ったり泣いたりします。
出前館も通常の会社と同じようにレガシーがあります。いや、多くあります。出前館は20年以上の歴史がある会社です。つまり、20年分のレガシーが蓄積されています。レガシーはコードやシステムだけではありません。スペックや業務プロセスにもレガシーがあります。例えば、店舗の属性の中に「2-in-1」というものがありますが、これを定義し、実装した形が3つありました。会社内でも「2-in-1店舗」と言っても、部署によって理解が違うわけです。これもレガシーと言えます。
現在、出前館はアプリからバックエンドまで、バックエンドから運用プロセスまで、ひとつひとつ作り直しながら、レガシーを刷新する長期的なプロジェクトを進めています。刷新の方法はいくつかありますが、今回は一般的な方法以外に少し変わった3つの方法を紹介したいと思います。
目次
- レガシーを解消する代表的な方法
- より過激にレガシーを解消する3つの方法
- 最後に
レガシーを解消する代表的な方法
レガシーを解消する代表的な方法としては、Strangler Fig パターンがあります。下図のように新しいシステムを用意した後、条件によって古いシステムから新しいシステムに移行し、新しいシステムがうまく機能すると確信したら、古いシステムへの経路をなくして閉鎖する方法です。直接見たことはありませんが、Strangler Figという木がこのような方法で生育するそうです。
出前館でもこの方法でバックエンドの様々なコンポーネントを積極的に改善しています。最も安全でありながら、結果とスケジュールを予測しやすい方法なので、運営中のサービスであればどこにでも適用することができます。
出前館でもこの方法でバックエンドの様々なコンポーネントを積極的に改善しています。最も安全でありながら、結果と日程を予測しやすい方法なので、運営中のサービスであればどこにでも適用することができます。出前館では決済、注文、会員情報など、eコマースとして最も中核のドメインにこの方法を使っています。
この方法の欠点としては、徐々に改善していく方法なので、かなり長い時間がかかるという点と、安定性を重視するだけに、レガシーの最も中核の部分は交替できない場合が多いという点が挙げられます。 そのため、より極端な方法も使われます。 どのような方法があるのか、一つずつ事例とともに紹介します。
より過激にレガシーを解消する3つの方法
どの方法を使うにしても、レガシーの解消は、ある時点でレガシーを断絶し、新しいシステムに置き換えることです。その断絶をどの程度深くするかによって、作業の難易度や規模が変わってきます。つまり、「過激である」ということは、それだけ深く、明確にレガシーと断絶しようとしたことを意味します。出前館でサービスを改善するために実際に使用した3つの過激なレガシー解消方法を、事例を交えてご紹介します。
- インフラを断絶する
- コードを書き直す(Recode)
- 仕様を軽量化する
1. インフラ(infrastructure)を断絶する
一単位のシステムは、インフラとそのインフラで動作するデータベースやアプリケーション、それらをつなぐメッセージングなど、複数のコンポーネントで構成されています。このうち、レガシーの多くはデータ構造に起因しています。 そのため、データ構造はそのまま残したまま、コードとパターンの一部だけを修正する作業は、レガシーを解消する努力に比べて成果があまり大きくない場合が多いです。 つまり、リファクタリングでより良いパターンを適用することも意味のある変化ですが、データ構造とそれを活用するデータ構成方法を変えることほど極端的な変化を作ることは難しいです。
3層(3-tier)構造でもヘキサゴナル(hexagonal)構造でも、ドメインとデータ層、アプリケーションなどは、ソフトウェア理論ではそれぞれを明確に区別する概念ですが、現実では曖昧な点があるものです(おそらく、何十年もの間、ドメイン設計に関する本がたくさん出てきたのもそのためかもしれません)。 つまり、ダイアグラムでは別々のコンポーネントのように描かれていますが、実際にはコードは混在している場合が多いのですが、この時、それぞれを理論のように明確に区別するにはどうすればいいのでしょうか。
一番良い方法は、物理的に分離することです。インフラを切り離せば、外部インターフェースのみで通信する必要があります。例えば、WebAPIでJSONやProtocol Buffersを活用することができます。こうすることで、自分が作ったシステムを一つの巨大なSaaS(Software as a Service)として扱う効果を得ることができます。つまり、お互いに外部システムとして扱うしかないように強制することになります。これにより、既存のデータ構造を考えずに、二つの環境間の通信のためのインターフェースとその中に入れるデータを最も理想的な形にするように誘導します(二つの環境間のインターフェースを考慮する時は、私の以前の記事オープンソースらしく設計するで拡張性 - 参加のための外部インターフェースを参考にしてください)。
この方法が成功するためには、チーム組織や運営の観点からも、最初から兼務や他のチームとの業務が重複することなく、一つのチームがサービス仕様からインフラのセットアップや開発まで独立して作業できるように分離する必要があります。
インフラ断絶方法の事例
出前館では、2022年にDeliveryのドメイン全体をインフラから他のドメインと切り離す作業を行いました。Delivery3.0というプロジェクトで、下図はDelivery 3.0移行時の2つのシステム間の認証フローを簡単に表したものです。システムを入れ替えても全員がログアウトされることなく、認証を維持したまま作業を進めたいため、アプリではレガシーシステムにログインした後、新しいシステムのトークンを再発行するフローを作りました。
リリースの際は、地域単位で半年間、4段階に分けて適用地域を拡大していきました。東京で選ばれた店舗を対象にリリースを開始し、次に西日本全域、その後東京全域、最後に日本全国に展開するまで、それぞれ2ヶ月単位でリリースを行いました。 幸いにも、出前館は1店舗単位で注文できるサービスという特徴があったため、店舗単位で新しいシステムの適用対象をコントロールすることができました。
出前館は、リアルタイムで人が動き、物やお金もリアルタイムで行き来する非常にダイナミックなサービスなので、地域単位で適用するたびにサービス品質が異なる可能性があり、CS(customer service)の状況にも注意を払う必要がありました。システムを作る側としては、1回だけ成功すれば、その後のすべてのフローも100%成功すると確信できますが、ユーザー側ではそうはいきません。 サービスの使い方に慣れていない加盟店主の操作ミスや、その地域に配達可能な配達員がいないなど、様々な理由ですべてのフローが100%成功するわけではなく、消費者、加盟店、配達員の3つのエンドユーザーの誰かがCSに問い合わせをする状況が発生します。
フードデリバリーは、世界的に「30分以内の配達」が重要なサービス指標の一つですが、出前館はDelivery 3.0プロジェクトの立ち上げ以来、これを達成してきただけでなく、2%未満の配達遅延、90%以上の配達満足度で世界レベルの配達品質を達成しています。
まとめると、環境を断絶して刷新する方法は以下のようなメリットがあります。
- 新しい機能を投入すると同時に、過去のシステムを自信を持って削除することができる。
- データ構造を新しい要求事項に合わせて再構成し、より良いパターンと構造を導入することができる。
- 既存システムの運用コストが高すぎる場合、移行するとすぐにコストを大幅に削減することができます。
しかし、すべての技術選択にはトレードオフがあるため、注意すべき点もあります。インフラ環境を切り離す方法は成功する確率が低いです。その理由は以下の通りです。
2.コードを書き直す(Recode)
Recodeは私が名付けた方法ですが、レガシーの仕様はそのまま維持した状態でコードを最初から書き直す方法を意味します。先に紹介したインフラの断絶方法は、以前のシステムが新しい機能を全く消化できないときに便利ですが、今回のセクションのRecodeはそれとは違って、旧バージョンと全く同じ仕様で作るという特徴があります。
Recodeをする理由とRecodeの長所と短所
一見理解できませんが、いったいなぜ同じ仕様で作り直すのでしょうか?通常、3つの状況があります。
- 仕様書(スペック文書)が流失してコードとプログラムしかない場合。
- 担当チームが変わり、既存のチームが作業した履歴を把握するのが難しい、またはアクセス権がない場合。
- 使用した技術より高度な技術が登場し、新しい技術セットへの移行が必要な場合。
この方法のメリットは下記の通りです。
- 既存のコードを分析するよりも、新たに作成する方が仕様を理解することに集中できる作業であり、成果物に対する確信も高くなる。
- 失われた仕様書と機能明細を一度に復旧することができる。
- 技術選択の自由度が高い。
デメリットを挙げると以下のようになります。
- コード書き換え期間中、仕様を凍結する必要があるため、ビジネス要件を受け取れない。
- 実力の足りないチームが作ると、新しく作った結果がもっと悪くなる可能性がある。
- バグが仕様になってしまったレガシーの場合、これを取り除く時に混乱が起きる可能性がある。
私がRecodeプロジェクトを何度も進めながら大きな効果を見た方法は、言語と技術セットを変えることです。先ほどのインフラ断絶の方法では、データへのアクセスを制約しましたが、Recodeでは言語を制約としてかけることができます。例えば、Pythonで書かれた既存のサーバーをRecodeではKotlinで書くということです。
通常、ある言語を使うとき、その言語のエコシステムの中で成熟したライブラリが推奨するパターンを使うことが多いです。そのため、言語を変えるとパターンが変わり、さらにアーキテクチャや状態管理も変わります。私は、このプロセスはプログラマーにとって非常に良いトレーニングになると思います。外国語を勉強する理由の一つが、他の文化をより幅広く理解するためであるように、扱える言語が増えると技術を理解する幅が広がります。
Recode適用の事例
出前館では2年間、4つのアプリを全てRecodeしました。 以前はアプリごとに言語からフレームワークまで全て異なっていました。ネイティブはもちろん、React NativeやXamarinなどもありましたが、Recodeによって結果的にFlutterに統一することができました。
モバイルアプリは、ユーザーがアップデートを受けるとパッケージ単位で完全に置き換えられるため、前述したStrangler Figパターンを適用するのは難しいです。だからといって、一つのアプリに複数の技術とフレームワークを混ぜるのも非効率的です。アップデートしながら保存されるのは端末内に保存されたログイントークンや簡単なテキスト情報だけである場合がほとんどです。 そのため、アプリの場合、たった1回のアップデートだけでレガシーをなくすこともできます。
下の図はRecodeプロジェクトを進める時、企画から開発までのスケジュールを大まかに示した図です。新しい企画を一旦中断することを宣言した後、約3ヶ月間現在の仕様通りにコードを新しく作成します。QAも1ヶ月またはそれ以上かかることもあります。
Recodeプロジェクトは内部は完全に新しく作成しますが、外部の動作は同一に維持します。それで、アップデートしたときにユーザーが変わったことを気づかななければ成功と言えるユニークなプロジェクトです。そのため、古いアプリと新しいアプリを並べて同じ経験を提供できるようにQAを行います。アップデート後は、「何が変わったのか分からないけど、反応が速くなった」程度の感想しかもらえません。
試行錯誤もありました。私たちはDriverアプリを最初の事例としてプロジェクトを進めたのですが、最初はReact Nativeで開発されたものを当時人気だったKMM(Kotlin Multiplatform Mobile)でRecodeしました。しかし、iOSのデバッグ経験が生産性と安定性を低下させる問題が発生し、再びFlutterに切り替えました。 つまり、Recode作業だけ2回したことになります。
Recodeの経験をもう少し詳しく聞きたい方は下記のシリーズを参考にしてください。
- 日本1位配信、底から作り直す
- 問題のないアプリをFlutterアプリに作り直した理由 - 日本1位のデリバリーアプリ、2回目のRecode
- Flutter切り替えのピリオド。
結果的に、現在Flutter技術については出前館が一番先導している会社の一つだと思います。 技術統合もし、レガシーも解消し、技術力も高くなったので、個人的にはとても完成度の高いレガシー解消プロジェクトだと思います。
Recodeはクライアントアプリだけに意味のある作業ではありません。バックエンドにも意味があります。DBだけそのまま維持した状態でJavaコードをTypeScript/Node.jsまたはC#/.NETに変えたり、あるいはJVMエコシステムから出るのが負担になるなら、KotlinまたはScalaに変えるだけでもより効率的なパターンを試すことができます。
3.仕様を軽量化する
レガシーになるのは、プログラムの問題よりも、プログラムで実現したいビジネス要求事項が原因である場合が多いです。だからといって、既存のビジネス要求事項とそれを実現した仕様を恨むことはできません。ソフトウェアの存在理由はビジネス要件の実現だからです。頻繁に変化する要件も、無理な要求も、結局は会社の存続と成長のための努力だからです。
サービス仕様の中には、業務への影響は大きくなくても、システムへの影響は大きい仕様があります。このような場合は、営業や運用チームと協議して仕様や機能を取り除く方法が必要ですが、営業や運用ではほとんどの場合、現在の仕様を維持したいと思っています。なぜなら、一度機能が完成してシステムで動作し始めると、一人でも影響を受けるユーザーがいるからです。 そのため、経営陣と話し合って決めるプロセスも必要です。
そのため、さまざまなレガシー解消方法の中で、既存の仕様をなくす方法が最も難しいと言えます。相談しないといけないチームも複数あり、影響を受けるユーザーの分析も必要で、運用からCSマニュアルにまで影響するため、作業期間が予定されたスケジュールよりずっと長くなることもあります。既存の仕様から生み出されている利益もあるため、その経済的な損失を甘受しないといけない理由をプロダクトの価値で説明する必要もあります。
仕様軽量化の適用事例
現在、出前館は、本格的なバックエンドの刷新に入る前に、仕様を軽量化する作業を進めています。例えば、出前館は、ユーザーがWebで注文すると、加盟店がその注文をFAXで受け取るというのが当初のビジネスモデルであったため、最近までFAXで注文書を送信する機能がありました。現在はほとんどタブレットに置き換えられましたが、一部の加盟店では特定の条件下でFAXで注文書を送信する機能を使い続けており、最近まで月に1000件ほど送信していました。
FAXを送信するには、FAX連携サーバーが必要で、FAX回線も必要です。出前館では、下図のように、ローカルデータセンターの1つの冗長化された物理サーバーでFAXサーバーを運用していましたが、昨年、FAXサーバーをSaaSサービスに切り替えることを検討しましたが、あまり効率的でない、または要求条件に合わなかったため、切り替えることができませんでした。代わりに、FAX仕様を完全に廃止することを検討し、今年7月にFAX仕様を廃止しました。
FAX仕様を削除した結果、ローカルデータセンターがなくなり、インフラコストが削減され、物理サーバーを監視する必要もなくなり、ロジックも単純化され、CSの負担も軽減されました。 もちろん、加盟店主の一部はFAX仕様を希望する人もいましたが、それよりもサービス全体がより健全に運営できるようにプロダクトを作ることが、サービスの持続可能性をより高める方向であると信じて実行しました。
私は持続可能なシステムを作るために、20年間蓄積された負債を大幅に減らしたかったので、経営陣を説得した挙句、システムを開発する時に経済的価値が曖昧なレガシー機能が大きな負担になる場合は、その機能を思い切って削除することに対し、経営陣の合意を得ました。会計上、数%以下の経済的損失は、将来、より健全なシステムにするために会社が負担するという基準も作りました。 先ほど申し上げたように、この方向がサービスの持続可能性をより高める方向だと信じているからです。
最後に
今回は、出前館で使用した、または使用中のレガシーの解消方法を紹介しました。これらの取り組みはすべて、地域のクイックコマース(Q-Commerce)をより理想的に実現するためです。
出前館のサービスの魅力は、人を動かすということです。人間が行う行為の中で最も複雑な行為が料理することと運転することだそうですが、この2つを職業としている方が出前館を利用しています。熱い火と湿気に満ちた所に置かれたタブレット端末から入る注文は、地域の加盟店主に笑顔をもたらします。自転車やバイクで道路を走る人たちは、加盟店からよろしくお願いしますという言葉と共に責任感を持って料理を受け取り、顧客に配達します。この一連の流れはとてもダイナミックで、熱気に満ちています。このように地域に活気を与えてくれている人たちのために、より良いサービスを作っていかなければならないと思い、その結果を今後、より良い記事で紹介したいと思います。