Slide 1

Slide 1 text

Eightにおける Androidアプリのリアーキテクチャ

Slide 2

Slide 2 text

⼭本純平(Jumpei Yamamoto) ೥ ݄ 4BOTBOגࣜձࣾ ೖࣾ &JHIU"OESPJEΞϓϦέʔγϣϯ։ൃ σʔλ෼ੳ ʮ,PUMJOΠϯΞΫγϣϯʯͷ຋༁ʹࢀՃ Eight事業部 モバイル・アプリケーショングループ

Slide 3

Slide 3 text

Sansan Builders Box 質問などはsli.doで https://app2.sli.do/event/qlew0bqo sli.do -> 「#SBB1-1」

Slide 4

Slide 4 text

Sansan Builders Box 本Sessionについて EightのV9リリースを前にAndroid版で⾏っている⼤規模なアー キテクチャの刷新について、どのような⽅向を⽬指しているの かについて紹介したいと思います。

Slide 5

Slide 5 text

Sansan Builders Box Agenda - データの流れを整理する - ドメインオブジェクトを定義する - マルチモジュール化 - まとめ

Slide 6

Slide 6 text

Sansan Builders Box 前提とする構成 • 基本的にはサーバのデータを取 得 or 更新するアプリケーション • ビジネスロジックはサーバ側で 実装 • データをサーバから取得 • ローカルのデータベースに キャッシュ • UIで表⽰ データベース UI サーバ

Slide 7

Slide 7 text

Sansan Builders Box Agenda - σʔλͷྲྀΕΛ੔ཧ͢Δ - ドメインオブジェクトを定義する - マルチモジュール化 - まとめ ࣭໰ͳͲ͸sli.do → ʮ#SBB1-1ʯ

Slide 8

Slide 8 text

データの流れを整理する ࣭໰ͳͲ͸sli.do → ʮ#SBB1-1ʯ

Slide 9

Slide 9 text

Sansan Builders Box ケース: データの更新を考える • 初回データをロードして表⽰ • メッセージ送信時に、表⽰を更新 • サーバからのPushをトリガーにし てメッセージを受信 クラウド දࣔΛߋ৽͢ΔτϦΨ͕ෳ਺͋ ΓɺҰͭͷActivity্ͰॲཧΛ͢ Δͷ͸େม Pushで表⽰の 更新 ⾃分でメッセージを送信 したときに更新

Slide 10

Slide 10 text

Sansan Builders Box 解決⽅法 • 扱うデータを⼀箇所で管理する • データの流れを統⼀する

Slide 11

Slide 11 text

Sansan Builders Box データの流れを整理するために 1. UIはモデルの変更を受け取ってそれを反映する 2. モデルの変更とUIの更新は分離する ࢀߟ: FluxΞʔΩςΫνϟ

Slide 12

Slide 12 text

データの流れに関する原則1 UIはモデルの変更を受け取ってそれを反映する

Slide 13

Slide 13 text

Sansan Builders Box • モデルはデータを⼀箇所で管理 • UIはモデルの変更をObserveする • UIの更新はモデルの変更通知からのみ⾏う UIはモデルの変更を受け取ってそれを反映する UI UI データベース サーバ

Slide 14

Slide 14 text

Sansan Builders Box UIにデータを供給するためのInterface • UIからみたモデルのレイヤをStoreと 名付ける。 • UIはStoreからデータを取得する • Storeはデータベースに変更があった 場合にUIに対して変更通知を送る UI Store データベース

Slide 15

Slide 15 text

Sansan Builders Box Storeインターフェイス interface ObjectStore{ val value: T fun updates(): Observable } interface ArrayStore{ val size: Int fun get(index: Int): T fun updates(): Observable } 0CKFDU4UPSF୯ҰͷΦϒ δΣΫτ "SSBZ4UPSFΦϒδΣΫτ ͷ$PMMFDUJPO 3Y+BWBΛ͔ͭͬͯ7JFXʹ มߋΛ௨஌͢Δ

Slide 16

Slide 16 text

Sansan Builders Box Storeのデータベースでの実装 • データベースライブラリにて、Storeイン ターフェイスを実装 • Realm • SQLite • SharedPreferences Store データベース

Slide 17

Slide 17 text

Sansan Builders Box データベースでの変更を通知する • Realm, SharedPreferencesにはデータベース⾃ ⾝に変更を通知する機能を持つ • SQLiteの場合は保存時にRxJavaのRxRelayを 使って、変更があったことをStoreに伝える Store データベース

Slide 18

Slide 18 text

データの流れに関する原則2 モデルの変更とUIの更新は分離する

Slide 19

Slide 19 text

Sansan Builders Box UIでサーバから値を取得し、更新する // ඇಉظͷ௨৴ίʔϧόοΫ view.setOnClickListener{ api.updateData(data){ saveData(it.response) textView.text = it.response } } Activityͷίʔυ UI サーバから取 得したデータ でUIを更新

Slide 20

Slide 20 text

Sansan Builders Box UseCaseクラス • UIでアクションがあった場合 は、UseCaseを実⾏する • UseCaseは通信等を⾏い、 データベースを書き換える UI データベース ΞΫγϣϯ UseCase อଘ

Slide 21

Slide 21 text

Sansan Builders Box UIからのアクション val usecase: updateDataUseCase = ... view.setOnClickListener { useCase.execute() } UIでのアクションに よってUseCaseを実⾏

Slide 22

Slide 22 text

Sansan Builders Box UseCaseの実装例 class GetPostUseCase (....): UseCase1 { override fun buildSingle(postId: PostId): Single = apiProvider.get().getPost(postId) .doOnNext { postRepository.savePost(it) } .single(Unit) }

Slide 23

Slide 23 text

Sansan Builders Box UseCaseの実装例 class GetPostUseCase (....): UseCase1 { override fun buildSingle(postId: PostId): Single = apiProvider.get().getPost(postId) .doOnNext { postRepository.savePost(it) } .single(Unit) } RxJavaのSingleを使い ioスレッドで実⾏

Slide 24

Slide 24 text

Sansan Builders Box UseCaseの実装例 class GetPostUseCase (....): UseCase1 { override fun buildSingle(postId: PostId): Single = apiProvider.get().getPost(postId) .doOnNext { postRepository.savePost(it) } .single(Unit) } サーバから データを取得

Slide 25

Slide 25 text

Sansan Builders Box UseCaseの実装例 class GetPostUseCase (....): UseCase1 { override fun buildSingle(postId: PostId): Single = apiProvider.get().getPost(postId) .doOnNext { postRepository.savePost(it) } .single(Unit) } データベースに保存

Slide 26

Slide 26 text

Sansan Builders Box UIアクションからのデータの流れを統⼀する UI データベース ΞΫγϣϯ UseCase อଘ

Slide 27

Slide 27 text

データの流れまとめ

Slide 28

Slide 28 text

Sansan Builders Box データの流れまとめ • UIはStoreからデータを 取得し表⽰を⾏う UI Store データベース UseCase දࣔ

Slide 29

Slide 29 text

Sansan Builders Box データの流れまとめ • Viewからデータの更新を ⾏う場合はUseCaseを利 ⽤してデータベースを更 新する UI Store データベース ΞΫγϣϯ UseCase อଘ

Slide 30

Slide 30 text

Sansan Builders Box データの流れまとめ • データベースの更新は Store経由でUIに通知さ れる UI Store データベース UseCase ௨஌

Slide 31

Slide 31 text

Sansan Builders Box データの流れまとめ • UIはStoreからの更新の 通知を受け取ってViewを 更新する UI Store データベース UseCase ߋ৽

Slide 32

Slide 32 text

Sansan Builders Box ケース: データの更新を考える データ更新のトリガが複数 あっても、UIはつねにStore からの更新のみを⾒ていれば よい クラウド Store データベース Push௨஌ ʹΑΔߋ৽ UseCase ViewͷτϦΨ ʹΑΔߋ৽

Slide 33

Slide 33 text

Sansan Builders Box データの流れについて、まとめ アプリ内で扱うデータの流れを整理することで、複数 の画⾯やバックグラウンドの通信など、複数の状況で 更新されるデータを簡単に管理することができる

Slide 34

Slide 34 text

Sansan Builders Box Agenda - データの流れを整理する - υϝΠϯΦϒδΣΫτΛఆٛ͢Δ - マルチモジュール化 - まとめ ࣭໰ͳͲ͸sli.do → ʮ#SBB1-1ʯ

Slide 35

Slide 35 text

ドメインオブジェクトを定義する ࣭໰ͳͲ͸sli.do → ʮ#SBB1-1ʯ

Slide 36

Slide 36 text

Sansan Builders Box ドメインオブジェクトを定義する • 画⾯の表⽰項⽬に合わせたドメインオブジェクト • プリミティブな型をラップする • ドメインオブジェクトとライブラリの依存関係

Slide 37

Slide 37 text

画⾯の表⽰項⽬に合わせた ドメインオブジェクト

Slide 38

Slide 38 text

Sansan Builders Box これまでの実装 • ローカルのデータベースに は基本的にサーバから取得 したJSONの構造がそのま ま保存される • UIではローカルのデータ ベースに保存されたデータ をそのまま使⽤していた UI Store データベース

Slide 39

Slide 39 text

Sansan Builders Box 保存データと表⽰項⽬のミスマッチ UIの表⽰ロジックが複雑化 σʔλϕʔε্Ͱͷఆٛ ໊લʹfirstCardͷfullName kindΛݟͯiconʹछผΛબ୒ ໊ࢗը૾ͷURL͸cardId͔Βߏங etc.

Slide 40

Slide 40 text

Sansan Builders Box Eightシステム全体から⾒た モバイル・アプリケーションの役割とは? • システムのドメインロジックはサーバ で実装 • モバイル・アプリケーションの⼤部分 の実装はサーバのデータを取得して表 ⽰、更新 • モバイル・アプリケーション側でドメ インロジックを持つことはほぼない モバイル アプリケーション サーバ ϞόΠϧɾΞϓϦέʔγϣϯ͸ ը໘දࣔʹಛԽͨ͠γεςϜͰ͋Δ

Slide 41

Slide 41 text

Sansan Builders Box ドメインオブジェクト ドメインオブジェクトは 画⾯上の表⽰項⽬に 特化したものとして定義

Slide 42

Slide 42 text

Sansan Builders Box 表⽰項⽬に特化したドメインオブジェクト σʔλϕʔε্Ͱͷఆٛ υϝΠϯΦϒδΣΫτ 表⽰⽤のiconリソースIDや URLを直接含む

Slide 43

Slide 43 text

Sansan Builders Box 表⽰項⽬に特化したドメインオブジェクト • データベースなどの外部ライブラリの影響を受けない • UI(ViewModelやPresenter等)で複雑な変換ロジックをも つ必要がない • ドメインオブジェクトへの変換処理とUIと切り離すことがで きる • アプリの表⽰仕様をドメインロジックとしてまとめることが できる

Slide 44

Slide 44 text

プリミティブ型をラップする

Slide 45

Slide 45 text

Sansan Builders Box 従来のオブジェクト定義 型だけでは何を表しているかわからないし、他のLong型の値も 代⼊できてしまうので、独⾃の型を定義したい。 でもアプリのいたるところでこの値が使われているし、これを置 き換えるのは⼤変なのでは idがLong型で定義されている!

Slide 46

Slide 46 text

Sansan Builders Box とりあえずdata classを定義してしまう • ⼀度にすべてのidを置き換えることは考えない • いつでもプリミティブ型の値が取り出せるので、元のソース と互換をとるのは簡単 • 置き換えやすいところから少しずつ置き換えていく • 置き換えたところでは確実に効果が出る! data class CardId(val rawValue: Long)

Slide 47

Slide 47 text

ドメインオブジェクトと ライブラリの依存関係

Slide 48

Slide 48 text

Sansan Builders Box これまでの実装 • UIからはStore経由でデータベースに 依存しているため、UIでもデータベー スの変更などの影響をうけてしまう UI Store データベース

Slide 49

Slide 49 text

Sansan Builders Box ドメインオブジェクトの導⼊ • UIはStoreインターフェイ スとドメインオブジェク トのみを参照 UI Store Interface ドメイン オブジェクト

Slide 50

Slide 50 text

Sansan Builders Box ドメインオブジェクトの導⼊ • UIはStoreインターフェイ スとドメインオブジェク トのみを参照 • データベースレイヤにて Storeインターフェイスを 実装 UI Store Interface ドメイン オブジェクト StoreImpl データベース

Slide 51

Slide 51 text

Sansan Builders Box ドメインオブジェクトの導⼊ • UIはStoreインターフェイ スとドメインオブジェク トのみを参照 • データベースレイヤにて Storeインターフェイスを 実装 • UIへはDIを使ってInject UI Store Interface ドメイン オブジェクト StoreImpl データベース Inject Component

Slide 52

Slide 52 text

Sansan Builders Box ドメインオブジェクトまとめ • イミュータブルなドメインオブジェクトを定義することに よって、アプリはデータベースなど個別のライブラリの影響 を最⼩限にとどめることができる • ビジネスロジックをサーバに持つモバイル・アプリケーショ ンはデータの閲覧に特化したシステムとしてドメインオブ ジェクトを作成する

Slide 53

Slide 53 text

Sansan Builders Box Agenda - データの流れを整理する - ドメインオブジェクトを定義する - ϚϧνϞδϡʔϧԽ - まとめ ࣭໰ͳͲ͸sli.do → ʮ#SBB1-1ʯ

Slide 54

Slide 54 text

マルチモジュール化 ࣭໰ͳͲ͸sli.do → ʮ#SBB1-1ʯ

Slide 55

Slide 55 text

Sansan Builders Box なぜマルチモジュールにするか? • ビルドの⾼速化 • ⼤規模なコードを分割してわかりやすくしたい • モジュール間の循環依存は許されないため、依存関係を強制した い

Slide 56

Slide 56 text

Sansan Builders Box モジュール分割⽅針 • ⽔平⽅向(レイヤごと)に分ける • 垂直⽅向(機能別)に分ける

Slide 57

Slide 57 text

Sansan Builders Box ドメインオブジェクトを導⼊したライブラリの依存関係 UI Store Interface ドメイン オブジェクト StoreImpl データベース Component

Slide 58

Slide 58 text

Sansan Builders Box ドメインオブジェクトを導⼊したライブラリの依存関係 UI Store Interface ドメイン オブジェクト StoreImpl データベース Component app: モジュール ui_component: モジュール repository: モジュール domain: モジュール

Slide 59

Slide 59 text

Sansan Builders Box ⽔平⽅向でのモジュール分割 ui_component: libraries: network: repository: domain: utils: app:

Slide 60

Slide 60 text

Sansan Builders Box ⽔平⽅向でのモジュール分割 • app: アプリケーションのエ ントリポイント、DIによる 依存関係の解決を⾏う ui_component: libraries: network: repository: domain: utils: app:

Slide 61

Slide 61 text

Sansan Builders Box ⽔平⽅向でのモジュール分割 • ui_component: Activityに始 まるUIのコード ui_component: libraries: network: repository: domain: utils: app:

Slide 62

Slide 62 text

Sansan Builders Box ⽔平⽅向でのモジュール分割 • libraries: Viewの共通ライブ ラリとUseCase ui_component: libraries: network: repository: domain: utils: app:

Slide 63

Slide 63 text

Sansan Builders Box ⽔平⽅向でのモジュール分割 • network: 通信系ライブラリ 群 ui_component: libraries: network: repository: domain: utils: app:

Slide 64

Slide 64 text

Sansan Builders Box ⽔平⽅向でのモジュール分割 • repository: データベース系 ライブラリ群。 ui_component:, libraries:か らは直接参照されない ui_component: libraries: network: repository: domain: utils: app:

Slide 65

Slide 65 text

Sansan Builders Box ⽔平⽅向でのモジュール分割 • domain: ドメインオブジェ クト、Storeインターフェイ ス ui_component: libraries: network: repository: domain: utils: app:

Slide 66

Slide 66 text

Sansan Builders Box ⽔平⽅向でのモジュール分割 • utils: 共通ユーティリティ ui_component: libraries: network: repository: domain: utils: app:

Slide 67

Slide 67 text

Sansan Builders Box ⽔平⽅向でのモジュール分割のメリット • 下位のレイヤが上位のレイヤ に依存していないことを保証 • 特にdomainモジュールがDB などの外部ライブラリに依存 しないこと • uiのモジュールがrepositoryモ ジュールを直接参照しないこ とを強制できる ui_component: libraries: network repository domain utils app:

Slide 68

Slide 68 text

Sansan Builders Box 垂直⽅向(機能ごと)のモジュール分割 メイン 画⾯ プロフィール 表⽰画⾯ カメラ 機能 …

Slide 69

Slide 69 text

Sansan Builders Box 機能別モジュール分割例 component main: component profile: component camera: library profile: camera device: domain: repository: app:

Slide 70

Slide 70 text

Sansan Builders Box 機能別モジュール分割例 • app: アプリケーションのエントリ ポイント、DIによる依存関係の解 決を⾏う component main: component profile: component camera: library profile: camera device: domain: repository: app:

Slide 71

Slide 71 text

Sansan Builders Box 機能別モジュール分割例 • component_xxx:はそれぞれの機 能毎のUI関連のコードが⼊るモ ジュール component main: component profile: component camera: library profile: camera device: domain: repository: app:

Slide 72

Slide 72 text

Sansan Builders Box 機能別モジュール分割例 • library_xxx: 機能毎のライブラリ とUseCase component main: component profile: component camera: library profile: camera device: domain: repository: app:

Slide 73

Slide 73 text

Sansan Builders Box 機能別モジュール分割例 • camera_device: cameraからしか 使⽤されないデバイス関連モ ジュール component main: component profile: component camera: library profile: camera device: domain: repository: app:

Slide 74

Slide 74 text

Sansan Builders Box 機能別モジュール分割例 • 機能毎にUI上のモジュールはわか れるが、各画⾯でドメインオブ ジェクトは共通に使⽤される component main: component profile: component camera: library profile: camera device: domain: repository: app:

Slide 75

Slide 75 text

Sansan Builders Box 機能別モジュール分割例 • データベースも、アプリ全体で relationを保っているため⼀つの モジュールとして実装される component main: component profile: component camera: library profile: camera device: domain: repository: app:

Slide 76

Slide 76 text

Sansan Builders Box 機能別モジュール分割例 component main: component profile: component camera: library profile: camera device: domain: repository: app: おそらくこの部 分も機能別に分 割できるはず

Slide 77

Slide 77 text

Sansan Builders Box 機能別モジュール分割のメリット component main: component profile: component camera: library profile: camera device: domain: repository: app: • 機能別にコードがまとまっている ので理解しやすい • 修正の影響範囲を最⼩限の抑える ことができる • ビルドを並列に実⾏することがで きる

Slide 78

Slide 78 text

Sansan Builders Box 循環依存するケース 画⾯遷移上、異なるモ ジュールのUIでも循環依存 んするケースが発⽣する ϓϩϑΟʔϧը໘ ϝοηʔδը໘ メッセージ を送る プロフィー ルを表⽰

Slide 79

Slide 79 text

Sansan Builders Box 循環依存するケース component profile: プロフィール表⽰ component message: メッセージ画⾯ app: ϝοηʔδը໘Λىಈ ϓϩϑΟʔϧը໘Λىಈ

Slide 80

Slide 80 text

Sansan Builders Box 共通のインターフェイスを定義 • 相互の画⾯の Intentを取得する ためのInterfaceを 準備 interface IntentResolver{ fun getProfileIntent(): Intent fun getMessageIntent(): Intent } component profile: プロフィール表⽰ component message: メッセージ画⾯ app:

Slide 81

Slide 81 text

Sansan Builders Box DI経由で解決 • 相互の画⾯の Intentを取得する ためのInterfaceを 準備 • app:モジュールに てその実装を⾏い、 各モジュールに Injectする interface IntentResolver{ fun getProfileIntent(): Intent fun getMessageIntent(): Intent } component profile: プロフィール表⽰ component message: メッセージ画⾯ class IntentResolverImpl: IntentResolver{ override fun getProfileIntent() = Intent(...) override fun getMessageIntent() = Intent(...) } app:

Slide 82

Slide 82 text

Sansan Builders Box 機能別モジュール分割のメリット • 機能別にコードがまとまっている ので理解しやすい • 修正の影響範囲を抑えることがで きる • ビルドを並列に実⾏することがで きる component main: component profile: component camera: library profile: camera device: domain: repository: app:

Slide 83

Slide 83 text

Sansan Builders Box マルチモジュールに移⾏するデメリット • モノリシックなアプリから移⾏する場合、依存関係を解決す るのは⾮常に⼤変 • singletonを多⽤ • staticにApplicationを参照 • ビルドの⾼速化の効果を計測するのは困難 • 検証する環境が同じになるとは限らない • モジュール分割とコードの追加が同時に⾏われることも • ビルド⾼速化だけを⽬的とするなら良いマシンを!

Slide 84

Slide 84 text

Sansan Builders Box マルチモジュール化まとめ • 機能毎にモジュールをまとめることにより、コードの影響範 囲を限定できる • マルチモジュール化によりモジュールの依存関係を強制する ことができる • 特にdomainモジュールをpureに保つことができる • 依存関係が循環する場合はDIにて解決する • マルチモジュール化は⼤変

Slide 85

Slide 85 text

Sansan Builders Box Agenda - データの流れを整理する - ドメインオブジェクトを定義する - マルチモジュール化 - ·ͱΊ ࣭໰ͳͲ͸sli.do → ʮ#SBB1-1ʯ

Slide 86

Slide 86 text

まとめ ࣭໰ͳͲ͸sli.do → ʮ#SBB1-1ʯ

Slide 87

Slide 87 text

Sansan Builders Box まとめ • データの流れを整理することによってActivityを始めとする UIの実装をシンプルにすることができます。 • モバイル・アプリケーションの場合、画⾯表⽰に特化したド メインオブジェクトを定義することによって実装がわかりや すくなります。 • アプリケーションのコードをモジュールに分割してその依存 関係を強制することで、全体的に理解しやすいコードにする ことができます。

Slide 88

Slide 88 text

Sansan Builders Box 質問などはsli.doで https://app2.sli.do/event/qlew0bqo sli.do -> 「#SBB1-1」

Slide 89

Slide 89 text

No content