Upgrade to Pro — share decks privately, control downloads, hide ads and more …

メルペイのスケーラビリティを支えるマルチモジュール開発

 メルペイのスケーラビリティを支えるマルチモジュール開発

メルペイのスケーラビリティを支えるマルチモジュール開発

メルペイ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のアーキテクチャ、新規モジュールでの採用も検討していきたいと思っています。

興味を持っていただけた方、ぜひ一緒にやっていきましょう。

以上で発表を終わります。ありがとうございました。

Masamichi Ueta

March 18, 2021
Tweet

More Decks by Masamichi Ueta

Other Decks in Programming

Transcript

  1. 6 • アプリを各層に分割 ◦ 層が分割されて構成がわかりやす くなる ◦ よく使う機能をまとめておける ◦ Extensionでも共通して使える

    ◦ ビルドタイムを短縮できる場合があ る アプリの構成 2 アプリターゲット ライブラリ1 ライブラリ2 UI エンティティ
  2. 7 • アプリの機能を、機能ごとに分割 する ◦ 対象の機能を備えたサンドボックスア プリを作って開発できる ◦ 複数チームで同時開発しやすくなる [参考]

    • モバイル決済アプリの作り方 @kenmaz https://speakerdeck.com/kenmaz/how-to-develop-a-mobil e-payment-app • Developing Apple Pay In-App Provisioning in merpay @kenmaz https://speakerdeck.com/kenmaz/developing-apple-pay-in -app-provisioning-in-merpay アプリの構成 3(メルカリ/メルペイ) アプリターゲット ライブラリ1 ライブラリ2 UI エンティティ 機能1 機能2 機能3
  3. 9 メルカリアプリ アプリターゲット 機能 機能 共通 共通 ・・・ 外部ライブラリ 外部ライブラリ

    ・・・ ・・・ デザインシステム 共通エンティティ ・・・ 共通外部ライブラリ 機能 機能 共通 共通 ・・・ 外部ライブラリ 外部ライブラリ ・・・ ・・・
  4. 10 メルカリアプリ アプリターゲット 機能 機能 共通 共通 ・・・ 外部ライブラリ 外部ライブラリ

    ・・・ ・・・ デザインシステム 共通エンティティ ・・・ 共通外部ライブラリ 機能 機能 共通 共通 ・・・ 外部ライブラリ 外部ライブラリ ・・・ ・・・
  5. 11 • 17個の機能モジュール、3個の共通 モジュール • 機能モジュール間で連携しつつも直 接の参照関係はない • 各機能開発時はそれぞれのサンド ボックスアプリで確認

    • 機能モジュールは全てスタティックラ イブラリ+専用バンドル、共通モ ジュールはダイナミックフレームワー ク クーポン QR NFC コア 通信 XXX メルペイの詳細 ・・・ アプリ アプリ アプリ アプリ スタティックライブラリ + バンドル ダイナミックフレームワーク
  6. 12 • 新規PJを立ち上げる場合は1コマン ドで新機能モジュールを生成 ./bin/init_framework.sh FRAMEWORK_NAME • 機能モジュールはスタティックライブ ラリなので、個数が増えても起動時 間に影響を与えない

    • 複数のプロジェクトを同時に開発し ていけるスケーラブルな構成 クーポン QR NFC New! コア 通信 XXX 新規プロジェクト立ち上げ アプリ アプリ アプリ アプリ アプリ スタティックライブラリ + バンドル ダイナミックフレームワーク ・・・
  7. 15 • 機能モジュール間での参照が複雑になってきた ◦ 機能モジュール間で相互に参照する必要が出てきた ◦ 1つの機能モジュールが複数の機能モジュールにリンクされる • 特定の機能モジュールに多くの機能が入っていた ◦

    新規モジュール作成に割と手間がかかる ◦ モジュールに分割すべき規模の指標がなかった ◦ xcodeprojのコンフリクト • モジュール分割したいが増えすぎても起動時間に影響を与えてしまう ◦ > You can reduce your app’s launch time by limiting the number of frameworks you embed https://developer.apple.com/documentation/xcode/improving_your_app_s_performance/reducing _your_app_s_launch_time • 分割したいけどなかなか分割できない状態 開発規模が大きくなるに連れて出てきた課題
  8. 16 • 機能モジュール間で直接参照を しなくても良い仕組みを作る ◦ 各モジュールに含まれる画面を抽象 化し共通モジュールに定義 ◦ 各モジュールから別のモジュールの 画面を呼び出せるようにする

    課題をどう解決していったか 1 public enum MerpayScene { case walletKit(scene: MerpayWalletKitScene) case nfcKit(scene: MerpayNFCtKitScene) ... } public class MerpaySceneFactory: MerpaySceneFactoryType { public static let shared = MerpaySceneFactory() private var factories: [MerpaySceneFactoryType] = [] public func register(factory: MerpaySceneFactoryType) { factories.append(factory) } public func viewController(for scene: MerpayScene) -> UIViewController? { for factory in factories { if let viewController = factory.viewController(for: scene) { return viewController } } return nil } guard let vc = dependency.viewController(for: scene) else { return }
  9. 17 • XcodeGenによるプロジェクト管理コスト低減 ◦ メルカリチームで @kateinoigakukun 中心にXcodeGen化進めていた ◦ メルカリに追従する形で XcodeGen化

    ▪ xcodeprojファイルのコンフリクトがなくなる ▪ 機能モジュールをテンプレート化してコマンドで生成することで新規モジュール作成の手 間が省ける ◦ 機能モジュールのサンドボックスアプリのターゲット追加も容易に ◦ XcodeGenではなくてもいいがマルチモジュール化には何らかの生成ツールが必要 課題をどう解決していったか 2
  10. 18 • 機能モジュール間の依存関係を整理、直接の参照を解消 ◦ 1で、機能モジュール間で直接参照しなくてもよくなったため、モジュール内で参照しあっていた 部分を別モジュールに分割 ◦ 機能モジュール間での直接の依存関係をなくす • 機能モジュールをスタティックライブラリ化

    ◦ 機能モジュール間での依存関係がなくなったことでスタティックライブラリ化が容易になった ◦ 機能モジュールが増えることによる起動時間への影響をなくす • 1つの大きなフレームワークを8個のスタティックライブラリに分割 ◦ 1つの機能モジュールの規模感もできてきた 課題をどう解決していったか 3
  11. 20 • 新規プロジェクト立ち上げ時の開発がやりやすくなった ◦ 新規機能モジュールの生成が容易 ◦ 機能モジュール間の連携方法が明確 ◦ 複数チームで複数の機能モジュール開発ができる •

    新たな開発プロセスもできるようになってきた ◦ 次のkuさんの発表で詳しく • モジュールごとのテストカバレッジも把握しやすくなった 現在のマルチモジュール開発構成でよくなったこと