メルペイのスケーラビリティを支えるマルチモジュール開発
メルペイiOSチームの上田雅道と申します。本日はメルペイのスケーラビリティを支えるマルチモジュール開発について発表致します。
本日お話することはこちらです。
まずマルチモジュール開発について、いくつかのパターンを紹介し、メルカリアプリ、メルペイの現在の状態について詳しくお話します。
次に、メルペイ立ち上げ時から現在の状態に至るまでの、構成の変化や課題、なぜ現在の構成にする必要があったかといった、プロセスについてお話します。
最後に今後取り組んで行きたいことを簡単にご紹介します。
まずマルチモジュール開発についてお話します。
この発表ではモジュールはフレームワークやスタティックライブラリと考えます。そしてマルチモジュール開発は複数のフレームワークやライブラリを組み合わせてアプリを開発することを言います。UIKitのようなSDKを考慮すると全てのアプリがマルチモジュールですが、ここではサードパーティーのライブラリや、自社プロジェクトのフレームワークを使う場合を想定しています。
まずマルチモジュールのよくある構成についてこのようなものがあると思います。
アプリのターゲットが1つあり、パッケージマネージャを使ってモジュールを管理するシンプルな構成です。新規のアプリだとこのような構成が多いかと思います。
次に、アプリを各レイヤーごとに分割する構成です。先程は1つだったところを、例えばUIコンポーネントのフレームワークや、データを表すエンティティなどに分割します。これによって層が分かれて構成がわかりやすくなる。よく使う汎用的な機能をまとめておける。Extensionでも共有して使える。ビルドタイムが減る場合があるなどの効果が期待できます。
次に、さらにアプリの機能を複数のモジュールに分割する構成が考えられます。メルカリ、メルペイの構成はこの構成をとっています。
機能ごとに分割することで、対象の機能を備えたサンドボックスアプリを作って開発できたり、複数チームで同時開発がしやすくなります。
より詳しくは、メルペイのkenmazさんの以下の資料も参考にしていただくとわかりやすいかと思います。
次にメルペイの現在の構成をさらに詳しく紹介したす。
メルペイの機能はメルカリアプリに統合されていますが、メルカリアプリ全体の構成はこのような形になっています。
メルカリのアプリに対して、大きくメルカリの機能モジュール群や共通モジュール、メルペイの機能モジュール群や共通モジュールがあり、またデザインシステムなど両方で共通して使っているモジュールもあります。
今日はこちらのメルペイ部分の詳細です。
メルペイは現在、17個の機能モジュールと、コア機能や通信など、3個の共通モジュールで構成されています。
依存関係をわかりやすくするために、機能モジュール間で互いに連携しつつも、直接モジュール間で参照はしていません。
各機能の開発は、それぞれの機能モジュール用のサンドボックスアプリで開発しています。もちろん、実際のメルカリアプリで確認する必要がある場合はそちらを使うこともあります。
そして、機能モジュールはすべてスタティックライブラリと、専用のバンドルで構成され、共通モジュールは複数の機能モジュールから読み込まれるため、ダイナミックフレームワークとなっています。
社内で新規プロジェクトが立ち上がり、新機能を開発していく場合は、1コマンドで新しいモジュールを生成することができます。
機能モジュールはスタティックライブラリなので個数が増えても起動時間に影響を与えません。
このようにメルペイでは複数のプロジェクトを同時に開発していけるスケーラブルな構成になっています。
現状はこうなっていますが、最初からこの規模ではありませんでしたし、少し構成が違っていたので、次にどういうプロセスを経てこの構成になったかを紹介します。
メルペイは立ち上げ時から、機能部分についてもマルチモジュール構成でした。リリースに想定される、機能モジュールが3個、共通モジュールが1つくらいの規模です。
すべてダイナミックフレームワークで、開発初期は機能間の依存もありませんでした。
メルペイリリースに向けて、開発を続けていましたが、開発規模が大きくなるにつれていくつか課題がでてきました。
まずは機能モジュール間での依存が複雑になってきました。機能モジュールが相互に参照する必要がでてきたり、メルカリへの統合もあったので、1つの機能モジュールが複数のモジュールにリンクされる、といったこともありました。
次に、分割しているものの、実際は特定の機能モジュールに多くの機能が入っていました。新規プロジェクトを作成するには手作業での設定など割と手間がかかりますし、モジュールに分割すべき規模の指標も特にありませんでした。また、xcodeprojファイルもコンフリクトすることがよくありました。
そして、大きなモジュールを分割できたとしても、モジュールが増えることによるアプリ起動時間への影響も考慮する必要がありました。
Appleのドキュメントにもフレームワークを数を減らすことで起動時間を短くできることが記されています。
このように、分割したくてもなかなか分割できない状態の時期がありました。
そこで社内で少しリファクタリングの時間をとって、課題を解決していきました。
まず、機能モジュールを分割していくにも、依存関係がより複雑になってはいけないので、機能モジュール間では直接参照しなくても、別モジュールの画面を呼び出せる仕組みを作りました。
共通モジュールにメルペイシーンという画面定義と必要なパラメータを定義し、各モジュールごとにシーンファクトリーを作ります。それを各モジュールのデペンデンシーに登録しておき画面を呼び出す、という方法をとっています。
また、これにより各モジュールの画面は全てインターナルなスコープにすることができるようになりました。
次に、Xcodegenを使ってプロジェクト管理コストをさげるようにしました。
ちょうどメルカリチームで、かていのいがくくんさん中心にXcodegen化を進めていて、それに追従する形でメルペイでも導入していきました。Xcodegenでプロジェクトを生成することで、xcodeprojファイルのコンフリクトがなくなりました。また新規モジュールに必要な構成をテンプレートとして保持し、新規モジュールをコマンドで生成することで、手間を減らし簡単に新規モジュールが作れるようになりました。このあたりはメルカリチームで進めていた仕組みをかなり参考にしています。
Xcodegenにしたことで、各モジュールへの新規ターゲットの追加も簡単になり、機能モジュールごとのサンドボックスアプリも作りやすくなりました。
マルチモジュール開発には、Xcodegenでなくても何かしらのプロジェクト生成ツールが必要だと感じています。
最後に既存の機能モジュール同士の依存を整理し、直接の参照を解消していきました。先程の1番のところで、機能モジュール間で直接参照しなくてもよくなったため、同じモジュール内の機能を別モジュールに移していきました。そして、既存の依存関係も解消していきました。
またそのタイミングで、機能モジュールはアプリターゲットのみからインポートされるため、スタティックライブラリにし、アプリに静的リンクするようにしました。これによって、モジュールが増えすぎることによる起動時間への影響もなくすことができ、むしろ起動を早めることができました。
実際には、大きくなっていた1つのモジュールを、8個のモジュールに分割し、1つの機能モジュールの大体の規模感の指標もできてきました。
こちらはスタティックライブラリ化したことによる起動時間の変化のサンプルです。特に性能の低いデバイスではわかりやすい結果となりました。
現在の構成になってよくなったことを改めて紹介すると、やはり新規プロジェクト立ち上げ時の開発がやりやすくなりました。新規モジュールの生成が簡単で、機能モジュール間の連携方法が明確です。複数チームで複数のモジュールを開発しやすくなっています。
また新しい開発プロセスもできるようになってきました。こちらは次のkuさんの発表をご覧ください。
モジュールごとのテストカバレッジも確認しやすくなっております。
今後取り組んでいきたいこととしては、メルペイのコードも規模が大きくなりモジュールが増え、フルビルドの時間も増えてきました。なにかしらでビルド時間を減らす方法はないか模索していきたいと思っています。
また、OSサポートの関係で、SwiftUIやCombineをまだ使っていないのですが、そろそろSwiftUIのアーキテクチャ、新規モジュールでの採用も検討していきたいと思っています。
興味を持っていただけた方、ぜひ一緒にやっていきましょう。
以上で発表を終わります。ありがとうございました。