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

EightにおけるAndroidのリアーキテクチャ/Android's re-architecture at Eight

Sansan
November 12, 2018

EightにおけるAndroidのリアーキテクチャ/Android's re-architecture at Eight

■イベント
Sansan Builders Box 2018
https://jp.corp-sansan.com/sbb2018/

■登壇概要
タイトル:「EightにおけるAndroidのリアーキテクチャ」
登壇者:Eight事業部 Mobile App Group エンジニア 山本 純平

▼Sansan Builders Box
https://buildersbox.corp-sansan.com/

Sansan

November 12, 2018
Tweet

More Decks by Sansan

Other Decks in Technology

Transcript

  1. Sansan Builders Box 前提とする構成 • 基本的にはサーバのデータを取 得 or 更新するアプリケーション •

    ビジネスロジックはサーバ側で 実装 • データをサーバから取得 • ローカルのデータベースに キャッシュ • UIで表⽰ データベース UI サーバ
  2. Sansan Builders Box ケース: データの更新を考える • 初回データをロードして表⽰ • メッセージ送信時に、表⽰を更新 •

    サーバからのPushをトリガーにし てメッセージを受信 クラウド දࣔΛߋ৽͢ΔτϦΨ͕ෳ਺͋ ΓɺҰͭͷActivity্ͰॲཧΛ͢ Δͷ͸େม Pushで表⽰の 更新 ⾃分でメッセージを送信 したときに更新
  3. Sansan Builders Box UIにデータを供給するためのInterface • UIからみたモデルのレイヤをStoreと 名付ける。 • UIはStoreからデータを取得する •

    Storeはデータベースに変更があった 場合にUIに対して変更通知を送る UI Store データベース
  4. Sansan Builders Box Storeインターフェイス interface ObjectStore<T>{ val value: T fun

    updates(): Observable<T> } interface ArrayStore<T>{ val size: Int fun get(index: Int): T fun updates(): Observable<Event> } 0CKFDU4UPSF୯ҰͷΦϒ δΣΫτ "SSBZ4UPSFΦϒδΣΫτ ͷ$PMMFDUJPO 3Y+BWBΛ͔ͭͬͯ7JFXʹ มߋΛ௨஌͢Δ
  5. Sansan Builders Box UIからのアクション val usecase: updateDataUseCase = ... view.setOnClickListener

    { useCase.execute() } UIでのアクションに よってUseCaseを実⾏
  6. Sansan Builders Box UseCaseの実装例 class GetPostUseCase (....): UseCase1<PostId, Unit> {

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

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

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

    override fun buildSingle(postId: PostId): Single<Unit> = apiProvider.get().getPost(postId) .doOnNext { postRepository.savePost(it) } .single(Unit) } データベースに保存
  10. Sansan Builders Box これまでの実装 • ローカルのデータベースに は基本的にサーバから取得 したJSONの構造がそのま ま保存される •

    UIではローカルのデータ ベースに保存されたデータ をそのまま使⽤していた UI Store データベース
  11. Sansan Builders Box Eightシステム全体から⾒た モバイル・アプリケーションの役割とは? • システムのドメインロジックはサーバ で実装 • モバイル・アプリケーションの⼤部分

    の実装はサーバのデータを取得して表 ⽰、更新 • モバイル・アプリケーション側でドメ インロジックを持つことはほぼない モバイル アプリケーション サーバ ϞόΠϧɾΞϓϦέʔγϣϯ͸ ը໘දࣔʹಛԽͨ͠γεςϜͰ͋Δ
  12. Sansan Builders Box 表⽰項⽬に特化したドメインオブジェクト • データベースなどの外部ライブラリの影響を受けない • UI(ViewModelやPresenter等)で複雑な変換ロジックをも つ必要がない •

    ドメインオブジェクトへの変換処理とUIと切り離すことがで きる • アプリの表⽰仕様をドメインロジックとしてまとめることが できる
  13. Sansan Builders Box とりあえずdata classを定義してしまう • ⼀度にすべてのidを置き換えることは考えない • いつでもプリミティブ型の値が取り出せるので、元のソース と互換をとるのは簡単

    • 置き換えやすいところから少しずつ置き換えていく • 置き換えたところでは確実に効果が出る! data class CardId(val rawValue: Long)
  14. Sansan Builders Box ドメインオブジェクトの導⼊ • UIはStoreインターフェイ スとドメインオブジェク トのみを参照 • データベースレイヤにて

    Storeインターフェイスを 実装 • UIへはDIを使ってInject UI Store Interface ドメイン オブジェクト StoreImpl データベース Inject Component
  15. Sansan Builders Box ドメインオブジェクトを導⼊したライブラリの依存関係 UI Store Interface ドメイン オブジェクト StoreImpl

    データベース Component app: モジュール ui_component: モジュール repository: モジュール domain: モジュール
  16. Sansan Builders Box ⽔平⽅向でのモジュール分割 • repository: データベース系 ライブラリ群。 ui_component:, libraries:か

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

    しないこと • uiのモジュールがrepositoryモ ジュールを直接参照しないこ とを強制できる ui_component: libraries: network repository domain utils app:
  18. Sansan Builders Box 機能別モジュール分割例 • library_xxx: 機能毎のライブラリ とUseCase component main:

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

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

    library profile: camera device: domain: repository: app: • 機能別にコードがまとまっている ので理解しやすい • 修正の影響範囲を最⼩限の抑える ことができる • ビルドを並列に実⾏することがで きる
  21. Sansan Builders Box 共通のインターフェイスを定義 • 相互の画⾯の Intentを取得する ためのInterfaceを 準備 interface

    IntentResolver{ fun getProfileIntent(): Intent fun getMessageIntent(): Intent } component profile: プロフィール表⽰ component message: メッセージ画⾯ app:
  22. 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:
  23. Sansan Builders Box 機能別モジュール分割のメリット • 機能別にコードがまとまっている ので理解しやすい • 修正の影響範囲を抑えることがで きる

    • ビルドを並列に実⾏することがで きる component main: component profile: component camera: library profile: camera device: domain: repository: app:
  24. Sansan Builders Box マルチモジュールに移⾏するデメリット • モノリシックなアプリから移⾏する場合、依存関係を解決す るのは⾮常に⼤変 • singletonを多⽤ •

    staticにApplicationを参照 • ビルドの⾼速化の効果を計測するのは困難 • 検証する環境が同じになるとは限らない • モジュール分割とコードの追加が同時に⾏われることも • ビルド⾼速化だけを⽬的とするなら良いマシンを!