エンジニアの渡邉です。普段はLIFULL HOME'Sの売買領域のエンジニアチームにて技術リーダーとして開発を担当しています。好きなNginxのモジュールはngx_small_lightです。
ここ数年、LIFULLの開発部門では「開発生産性」と「品質担保」の重要性が再注目されています。
LIFULL HOME'Sの主要なリポジトリは、10年以上にわたり運用され続けており、数多くの開発者が日々の改善に尽力しています。
しかし、長年にわたる蓄積によって、アプリケーションの要件を満たすための実装が複雑化し、現在では実装時に調査、開発、レビュー、テストのすべての工程でそれぞれ必要以上に時間がかかる結果となっており、開発の生産性を低下させています。
この問題に対処するため、LIFULL HOME'Sでは既存のアプリケーションから必要に応じてシステムを切り出し、部門ごとでの運用管理を行っています。
売買部門もこのアプローチでの分割を進め、不動産の「売買」に関わるアプリケーションを新基盤に移行しました。
そこで、今回はアプリケーションをリプレイスする際に考慮したことその成果についてお話させていただきます。
リニューアル前後のLIFULL HOME'S
ユーザー体験の改善
基盤刷新のタイミングでUI/UXの改善に踏み切り、レスポンシブデザイン化による体験の統一、アクセシビリティに配慮し、スクリーンリーダーやキーボードでも利用可能にするなど、WAI-ARIAを使用したUIマークアップやAccessible Perceptual Contrast Algorithm(APCA)の値が60を超えた高いコントラスト比を適用した結果、アクセシビリティスコアが改善されました。
物件詳細ページ
物件一覧ページ
※ 画像は開発中のものです。
パフォーマンスの改善
レスポンススピードの改善により、スムーズにページが閲覧できるようになりました。またページの読み込みパフォーマンス、インタラクティブ性、視覚的安定性が向上し、Core Web Vitalsのスコアも改善しています。
リプレイスしたアプリケーションには以下の技術を採用しています。
項目 | 技術 |
---|---|
アーキテクチャ | Clean Architecture |
フレームワーク | Express, LoopBack x TypeScript |
HTML | Preact x TypeScript |
CSS | Tailwind CSS |
JavaScript | Stimulus, Catalyst |
インフラ基盤 | Kubernetes |
LIFULL HOME'Sの抱えていた課題とその対応
開発プロセスやコードを簡素化したい
現在、LIFULL HOME'Sは50人以上のエンジニアによって運営され、年間1000件以上のリリースが行われています。
開発が始まって10年以上が経過し、プロジェクトが成熟するにつれ、ソースコードの複雑性が高まってきました。この複雑性は、リリースの速度を落とし、ソースコードの品質を年々劣化させる原因の一つです。
開発プロセスには、調査、設計、実装、レビュー、テスト、リリースという一連の段階が含まれます。しかし、10年以上の開発を経る中で増大したコードの複雑さは、過去の仕様が入り組んでいることや、古い技術を使用しているため、各工程の進行を妨げ、プロジェクトの期間を不必要に延ばしています。
これに対処するために、効率的な開発プロセスとコードの簡素化に向けた取り組みが求められています。
元々、LIFULL HOME'Sは賃貸と売買の領域が共存するアプリケーションでした。これらの領域は表面的に似ているように感じられるかもしれませんが、実際には多くの点で異なっていました。それにもかかわらず、無理な共通化を進めた結果、賃貸と売買の領域での差異を吸収するようなコードが増加し、システムの複雑性が時間とともに増していきました。これにより、少しの変更が多くの部分に影響を及ぼし、売買領域の機能改修が賃貸領域に予期せぬ影響を与えることが頻繁に発生しました。この結果、リリースごとに他部署との調整や、調査、実装、テストの工数が増大しました。
そこで、新しい基盤への移行に当たり、元々同一のプロダクトとして扱っていたものを分離し、賃貸と売買を別々のプロダクトとして扱うことで、無理な共通化を排除しました。
設計思想をわかりやすく伝達したい
LIFULL HOME'SはもともとMVCモデルで開発されていたアプリケーションでしたが、開始時は少人数であったため、設計方針が適切に伝達されていました。
しかし、時間が経過するにつれて開発者の数が増加し、初期の設計思想の伝達が不十分となりました。
この結果、レビュアーと開発者間の合意が優先されるようになり、当初の設計方針と異なる実装が増加しました。
これにより、多様な思想が混在したアプリケーションが形成されました。
この問題に対処するために、実装者とレビュアーの合意のみに頼らず、アプリケーションデザインを重視して設計方針を徹底的に管理し、見通しの良いアプリケーションを目指す方針に切り替えました。
その実現手段として、レイヤードアーキテクチャの一種であるクリーンアーキテクチャを採用し、責務を適切に設定し、依存関係を明確化しました。
また、既存のLIFULL HOME'Sでは似たようなドメインロジックの再実装が多くの箇所で頻繁に行われており、仕様変更時に多数の実装箇所を変更する必要がありました。
この問題に対応するため、不動産という扱いやすいドメイン特性を活かし、ドメイン層にドメインロジックを集約し、再利用可能なクリーンアーキテクチャを採用しました。
データ構造を把握しやすくしたい
LIFULL HOME'SはもともとPHP、Rubyを主言語として開発されていたアプリケーションでした。
年数を重ねソースコードの量が増加していく中で、非常に多くのデータフローをたどり、さまざまなデータが変数に入ることが懸念される状況でした。
また不動産にまつわる情報も多岐に渡るため、正規化しづらく、余計にデータの内容に対する信頼は低い状態でした。
したがって、実装時にも型やデータ形式等の可能性をひとつずつ検証し、レビュー時も実際のデータを見てみないとわからないなど、調査、設計、実装、テストそれぞれの工数が大きくなり、品質的にも良くない状況にありました。
そこで、静的型付け言語であるTypeScriptを採用しました。 数ある静的型付け言語の中でもTypeScriptを採用したのは、フロントエンドとバックエンドの両方を同一の言語で実装できる利点と、全社の技術方針をTypeScriptに寄せようという動きがあったためです。
TypeScriptを採用した理由などの詳細な内容については、 以前寄稿されたこちらの記事に詳細が記載されていますので、気になる方はぜひこちらもご覧ください。
成果
リリース速度の改善
アプリケーションを領域ごとに分割することで、アプリケーション内での責任範囲が明確になり、他のマーケットへの影響を避け、リグレッションチェックが不要となりました。
これにより、テスト時に考慮すべきポイントが減少しました。
さらに、自治権を自部署内で完結させたことで、調整コストの削減やリリースタイミングの管理が容易になり、最短でリリース承認から本番リリースまでを一日で完了できるようになりました。そのため、ユーザーへのプロダクト提供までの時間を短縮できました。
設計意図の伝達の容易化
厳格な制約を課すクリーンアーキテクチャを取り入れることで、独自の思想を持つアプリケーションの意図を理解するのが容易になりました。
クリーンアーキテクチャの採用により、後からプロジェクトに参加した開発者も迷わず開発を進めることができます。
開発プロセスの工数削減
クリーンアーキテクチャや静的型付け言語の採用により、強い設計思想やデータ構造の明確化が促され、設計に関する相談が減少しました。
また、データ構造の明確化やドメインロジックのドメイン層への集約化により、既存仕様の理解やバグ修正の工数が短縮されました。
静的型付け言語を採用したメリットも大きく型定義がしっかりしていることで実装者もレビュアーもデータを正しく認識したうえでソースコードが追えるようになり、工数の軽減に成功しました。
さらに、基盤の刷新により、パソコンとスマートフォンでの見た目を統一するレスポンシブデザインを採用して開発工数を削減しました。
今後の課題
複数アプリケーションに適用したい処理の取り扱い
アプリケーションを分割することで、新たな課題が生まれました。 特定のドメインロジックを共通で利用したいといった要望や、ABテストを行うための方法などのどのアプリケーションにも必要になりそうなものをそれぞれのアプリケーションごとに実装したくないといった課題です。
このような状況に対応するため、効率的なリソースの活用を目指し、再利用可能なコンポーネントを慎重に選択し、パッケージ化による再開発の回避を進めています。 パッケージ化は一定の調整を必要としますが、このプロセスにより、多くのアプリケーションが一貫性を持つ方法で利益を受けることが可能になります。
完全にパッケージ化されたソリューションを提供するための調整はありますが、これは品質の向上と開発プロセスの高速化に向けての積極的な取り組みの一環です。
私たちは、必要なアップデートを定期的に行い、より少ない工数で高品質な成果を提供することに努めています。
クリーンアーキテクチャの依存関係保護の仕組み
クリーンアーキテクチャに触れたことがある人なら、最初はどのレイヤーがどのレイヤーに依存して良いのかなど、覚えるべきことが多く苦労したことがあるでしょう。
クリーンアーキテクチャは強い制約があるため設計方針が崩れにくい一方で、最初に全体感を把握することは難しいです。
当初はCodeOwnerによるレビューを必須化し、依存関係の保全に努めていましたが、人間の目だけではどうしても抜け漏れが出てしまい、この方法が厳しいという結論に至りました。
そこで、dependency-cruiserによる制御を導入し、CodeOwnerのレビューに加えてシステマティックな保全を図りました。この方法では、dependency-cruiserの設定を徹底的に検証することで、違反が発生する可能性を格段に減らし、開発者がクリーンアーキテクチャを正確に理解していなくても、レイヤー間の依存関係による破壊を避けることができ、目標であったソースコードの品質保証を実現しました。
クリーンアーキテクチャを人の目だけで管理するのは困難です。したがって、このシステム化された保全方法を今後も積極的に導入していく予定です。
アプリケーションをどこまで分割して基盤刷新するか
新規に売買領域のアプリケーションを切り出すことになった際、2つの分割方法を検討しました。 それは以下の二つです。
- BFF層とフロントエンド用アプリケーションのパターン
- 売買領域を一まとめにし、モノリスアプリケーションとして運用するパターン
これらのアプリケーションを運用した経験から、それぞれに特有のメリットとデメリットが明らかになりました。
BFFとフロントエンドを分離したアプリケーションでは、フロントエンドのみの変更を行いたい場合、最小限の調査と実装コストでリリースが可能です。 また、バックエンドエンジニアとフロントエンドエンジニアの担当領域が完全に分かれているため、開発のしやすさとコンフリクトのリスク低減のメリットがあります。 I/Oの仕様が合意されていれば、各エンジニアが得意な方法で実装することが可能です。 さらに、BFF層を分けることで他のアプリケーションからのリクエストに柔軟に対応できる利点もあります。 ただし、リリース時には二度手間がかかるデメリットがあり、二つのアプリケーションに関わる修正を行う場合、リリースタイミングによるデータ不整合のリスクが常にありました。 そのため、データが変更されても問題なく機能するように実装する必要がありました。 また、BFFとフロントエンド間のデータ交換はすべてプリミティブな値で行われるため、フロントエンド側でも型定義が必要な二度手間が生じました。
モノリス形態のアプリケーションのメリットは、リリースの頻度が一度で済むため、リリースのタイミングを気にする必要がないです。 また、ドメイン層のValueObjectをフロントエンドでも利用できる設計にしているため、フロントエンド側で新たに型定義を作成することなく使用でき、ドメインロジックの利用が可能になりました。 これにより、フロントエンドでのロジック実装が削減できます。 しかし、このアプローチではフロントエンドエンジニアとバックエンドエンジニアの作業領域が衝突する頻度が増え、レイヤードアーキテクチャ内での役割分担の明確化が必要となりました。
以上のように、どちらのアプリケーションにも運用面、実装面での明確なメリットとデメリットがありました。
そのため、今後も最適な形を追求しながらアップデートを続けていく予定です。
終わりに
アプリケーションの再構築には、我々に大きな変化をもたらす可能性が秘められています。課題感を徹底的に分析し、アプリケーションの役割を明確にすることで、そのシンプル性が増すことが期待されます。時には、既存のアプリケーションを思い切って捨て去り、新たな軌道に乗ることで、画期的な進化を遂げるチャンスをつかむこともあります。
当然、新しいアプリケーションへの移行プロセスは、時間がかかり非常に困難なものです。知られざる仕様や対応の漏れなど、予想外の障壁に直面することも少なくありません。
しかし、長期的な視野に立てば、ソースコードの知識を未来世代に継承するとともに、開発生産性を飛躍的に高める絶好の機会であるとも言えます。これは、時に勇気を持ってアプリケーションの全面的なリプレイスを決断する理由となります。
本稿では、技術的な詳細には深く踏み込んでいませんが、その点については今後別の機会で詳しく掘り下げていく予定です。
最後に、LIFULL ではともに成長していける仲間を募集しています。よろしければこちらのページもご覧ください。