Slide 1

Slide 1 text

#MerpayTechFest Session Title メルペイ スケーラビリティを支える マルチモジュール開発 Masamichi Ueta Product Engineering, iOS team

Slide 2

Slide 2 text

#MerpayTechFest Product Engineering, iOS team Masamichi Ueta 2018年メルペイに入社し、メルカリ連携や旧メルペイトップ画面 開発を行う。 FlutterやSwiftUIでメルカリ TechConfアプリも作ったりしました。

Slide 3

Slide 3 text

#MerpayTechFest 本日 内容 マルチモジュール開発について メルペイ 構成 モジュール間連携 仕組み マルチモジュールを活用した開発プロセス 02 03 04 01

Slide 4

Slide 4 text

#MerpayTechFest マルチモジュール開発について

Slide 5

Slide 5 text

#MerpayTechFest マルチモジュール開発について ● モジュール ○ フレームワークやライブラリ ● マルチモジュール開発 ○ 複数 フレームワークやライブラリを組み合わせてアプリを開発すること

Slide 6

Slide 6 text

#MerpayTechFest アプリ 構成 Monolith ● 1つ アプリターゲット ● パッケージマネージャーを使ってライ ブラリを管理 アプリケーション ライブラリ1 ライブラリ2

Slide 7

Slide 7 text

#MerpayTechFest アプリ 構成 Layer ● アプリを各層に分割 ○ 層が分割されて構成がわかりやすくなる ○ よく使う機能をまとめておける ○ Extensionでも共通して使える ○ ビルドタイムを短縮できる場合がある ライブラリ1 ライブラリ2 UI モデル アプリケーション

Slide 8

Slide 8 text

#MerpayTechFest アプリ 構成 Feature ● アプリ 機能を、機能ごとに分割す る ○ 対象 機能を備えたサンドボックスアプ リを作って開発できる ○ 複数チームで同時開発しやすくなる ライブラリ1 ライブラリ2 アプリケーション 機能1 機能2 UI モデル

Slide 9

Slide 9 text

#MerpayTechFest メルペイ 構成

Slide 10

Slide 10 text

#MerpayTechFest Design System gRPC client External libraries Shared Merpay Mercari Mercari App Core API External libraries Shared Feature NFC Coupon QR more Dynamic framework (Mainly) Static library + Bundle Dependency Injection ※ Mercari depends Merpay shared modules a bit Dynamic framework

Slide 11

Slide 11 text

#MerpayTechFest Design System gRPC client External libraries Shared Merpay Mercari Mercari App Core API External libraries Shared Feature NFC Coupon QR more Dynamic framework (Mainly) Static library + Bundle Dependency Injection ※ Mercari depends Merpay shared modules a bit Dynamic framework ● 18個 機能モジュール ○ スタティックライブラリ +バンドル ○ モジュール間で 参照 ない ● 3個 共通モジュール ○ ダイナミックフレームワーク ● XcodeGenでプロジェクト管理

Slide 12

Slide 12 text

#MerpayTechFest メルペイ プロジェクト組織構成 iOS team PJ A PJ B PJ C Android team Backend team エンジニア

Slide 13

Slide 13 text

#MerpayTechFest メルペイ プロジェクト組織構成 iOS team PJ B PJ C Android team Backend team PJ D PJ A(完了) エンジニア

Slide 14

Slide 14 text

#MerpayTechFest メルペイ プロジェクト組織構成 iOS team PJ B PJ C Android team Backend team PJ D 新規モジュール! PJ A(完了) エンジニア

Slide 15

Slide 15 text

#MerpayTechFest 新機能開発 ● テンプレートから 新モジュールを生成 ./bin/init_framework.sh MerpayTechFestKit ● モジュールテンプレートに含まれるも ○ XcodeGen 設定ファイル ○ 各種ターゲット フォルダ ■ 機能 (スタティックライブラリ + バンドル)、テスト ■ サンドボックスアプリ ■ プレビューアプリ ● Xcode Previews でUIKitベース アプリ開発を効率化する - iOSDC Japan 2020 @kenmaz ● https://speakerdeck.com/kenmaz/xcode-previews-deuikitbesufalseapurikai-fa-woxiao-lu-hua-suru-iosdc-japan-2020

Slide 16

Slide 16 text

#MerpayTechFest 実際にやってみる ./bin/init_framework.sh MerpayTechFestKit Merpay.xcworkspace/contents.xcworkspacedata

Slide 17

Slide 17 text

#MerpayTechFest 実際にやってみる

Slide 18

Slide 18 text

#MerpayTechFest 実際にやってみる public final class MerpayTechFestKitSandboxViewcontroller: SandboxBaseViewController { public override init(style: UITableView.Style) { super.init(style: style) sections = [ .init(title: “Sample”, items: [ .init(title: “Hello MerpayTechFest”, action: { // Show View }) ]) ] } }

Slide 19

Slide 19 text

#MerpayTechFest 現在 構成 利点 ● 各プロジェクトで独立して開発しやすい ● アプリ全体をフルビルドしなくてもサンドボックスアプリで開発できる ○ Developing Apple Pay In-App Provisioning in merpay @kenmaz ○ https://speakerdeck.com/kenmaz/developing-apple-pay-in-app-provisioning-in-merpay ● スタティックライブラリな で、モジュールが増えても起動時間に影響を与えない ○ > 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_launc h_time

Slide 20

Slide 20 text

#MerpayTechFest 現在 構成 利点 ● プロジェクトファイルをXcodeGenで管理している でコンフリクトが発生しない ○ 何らか プロジェクト管理ツール 必要 ● 新プロジェクト立ち上げ時に新モジュールを作ることでモチベーションがあがる ○ これから育てていく感 ○ オーナーシップ

Slide 21

Slide 21 text

#MerpayTechFest チーム内で ナレッジシェア ● 自分が関わったモジュール以外 機能がわかりづらい ○ チームミーティングで具体的なコードや機能をシェアする時間を設ける ○ 週替わりで発表 ● 他 モジュール 似たようなフローを参考にする

Slide 22

Slide 22 text

#MerpayTechFest モジュール間連携 仕組み

Slide 23

Slide 23 text

#MerpayTechFest モジュール間連携 ● モジュール間で画面遷移が必要に なる ● モジュール間で直接参照すると関係 が複雑になる NFC Coupon QR

Slide 24

Slide 24 text

#MerpayTechFest モジュール間連携 ● モジュール間で直接参照しない ● Dependency Injectionで連携 できる仕組みを構築 NFC Coupon QR Dependency Injection Merpay Feature Core Shared

Slide 25

Slide 25 text

#MerpayTechFest public enum MerpayScene { case nfcKit(scene: NFCKitScene) case qrKit(scene: QRKitScene) case couponKit(scene: CouponKitScene) … } extension MerpayScene { public enum NFCKitScene { case scene1(argument: Scene1Argument) case scene2(argument: Scene2Argument) ... } public enum QRKitScene { case scene1(argument: Scene1Argument) case scene2(argument: Scene2Argument) ... } ... } MerpayScene NFC Coupon QR Dependency Injection Merpay Feature Core Shared

Slide 26

Slide 26 text

#MerpayTechFest MerpaySceneFactory NFC Coupon QR Dependency Injection Merpay Feature Core Shared public protocol MerpaySceneFactoryType { func viewController(for scene: MerpayScene, dependency: Dependency) -> UIViewController? } public class MerpaySceneFactory: MerpaySceneFactoryType { private var factories: [MerpaySceneFactoryType] = [] public func register(factory: MerpaySceneFactoryType) { factories.append(factory) } public func viewController(for scene: MerpayScene, dependency: Dependency) -> UIViewController? { for factory in factories { if let viewController = factory.viewController( for: scene, dependency: dependency) { return viewController } } return nil } }

Slide 27

Slide 27 text

#MerpayTechFest Dependency NFC Coupon QR Dependency Injection Merpay Feature Core Shared public protocol Dependency { ... var sceneFactory: MerpaySceneFactoryType { get } ... }

Slide 28

Slide 28 text

#MerpayTechFest MerpaySceneFactory NFC Coupon QR Dependency Injection Merpay Feature Core Shared import Core final class NFCKitSceneFactory: MerpaySceneFactoryType { func viewController(for scene: MerpayScene, dependency: Dependency) -> UIViewController? { guard case .nfcKit(let scene) = scene else { return nil } switch scene { case .scene1(let argument): return Scene1ViewController( argument:.init(aaa: argument.aaa), dependency: dependency ) case .scene2(let argument): return Scene2ViewController( argument:.init(aaa: argument.aaa), dependency: dependency ) ... } } }

Slide 29

Slide 29 text

#MerpayTechFest MerpaySceneFactory NFC Coupon QR Dependency Injection Merpay Feature Core Shared import Core import NFCKit import QRKit import CouponKit func application(xxx, didFinishLaunchingWithOptions ...) -> Bool { ... let sceneFactory = MerpaySceneFactory() sceneFactory.register(NFCKitSceneFactory()) sceneFactory.register(QRKitSceneFactory()) sceneFactory.register(CouponKitSceneFactory()) let dependency = Dependency( ..., sceneFactory: sceneFactory ) let rootVC = RootViewController(dependency: dependency) window?.rootViewController = rootVC ... }

Slide 30

Slide 30 text

#MerpayTechFest MerpaySceneFactory NFC Coupon QR Dependency Injection Merpay Feature Core Shared import Core final class Scene1ViewController: UIViewController, Instantiatable { init(argument: Argument, dependency: Dependency) { self.dependency = dependency ... } func showQR() { guard let vc = dependency.sceneFactory.viewController( for: .qrKit(.scene1(argument: .init(aaa: aaa)))) else { return } navigationController?.pushViewController(vc, animated: true) } }

Slide 31

Slide 31 text

#MerpayTechFest モジュール間連携 ● モジュール間で直接参照しなくてよい ● モジュール内 ViewController 全て internal にできる ○ 同じモジュール内 SceneFactory で生成される ○ 使う時 UIViewController で抽象化される

Slide 32

Slide 32 text

#MerpayTechFest マルチモジュールを活用した 開発プロセス

Slide 33

Slide 33 text

#MerpayTechFest メルペイで 機能開発プロセス ● 新規モジュールを作る ./bin/init_framework.sh MerpayTechFestKit ● Featureブランチを作る git checkout -b feature/merpay-tech-fest ● デイリーでmainブランチをマージしつつ開発 ● FeatureブランチでメルカリアプリをビルドしてQA

Slide 34

Slide 34 text

#MerpayTechFest リリース

Slide 35

Slide 35 text

#MerpayTechFest 問題 ● コンフリクト ○ デイリーでmainブランチをマージする時によくコンフリクトが発生する ○ 毎日ブランチを管理するコストがかかる ● リリース時 マージ ○ Feature リリースが重なるとコンフリクトが大きくなる可能性が高い ○ 本来 全て コードがマージされている状態でQAすべき

Slide 36

Slide 36 text

#MerpayTechFest 理想 ● 開発中 コードもmainブランチにマージ ● 新規モジュール リリースまでアプリに入れない ○ アプリサイズ ○ 情報漏洩

Slide 37

Slide 37 text

#MerpayTechFest 最近取り入れている方法 ● 新モジュール コードをmainブランチにマージ ● ただし、アプリ本体に リンクしない ○ モジュールをリンクしなけれ リリースされるバイナリに 含まれない ○ 機能開発 サンドボックスアプリで進める

Slide 38

Slide 38 text

#MerpayTechFest 最近取り入れている方法 ● Featureブランチ 最終的に 必要 ○ 既存 機能へ 変更 ○ モジュールをリンクしたアプリ QA ● それでもFeatureブランチに全部入れておくより ○ mainブランチと diffがかなり減る ○ 他 ブランチと コンフリクト 可能性も減る ● マルチモジュールだからこそできる開発プロセス ○ Feature branchを使わないFeature開発 @ku ○ https://speakerdeck.com/ku0522a/number-merpay-techtalk-feature-branchwoshi-wanaifeaturekai-fa

Slide 39

Slide 39 text

#MerpayTechFest メルペイ スケーラビリティを支える マルチモジュール開発 ● たくさん プロジェクトが同時に進んでいても、それぞれ チームが独立して小さ く速く開発していける構成 ● モジュールが増えても起動時間に影響しない ● モジュール間連携 仕組みによって、モジュール間 依存関係がないクリーンな 構成 ● マルチモジュールならで ブランチマネジメント