Slide 1

Slide 1 text

Pococha Androidにおける マルチモジュール化の取り組み ライブストリーミング事業本部Pococha事業部システム部 田嶋秀成

Slide 2

Slide 2 text

2 自己紹介 ● 氏名:田嶋秀成 ● 2022年DeNA新卒入社 ● Androidエンジニア ● 趣味はArch Linuxを育てること

Slide 3

Slide 3 text

3 はじめに Androidアプリのマルチモジュール化、やっていますか?

Slide 4

Slide 4 text

4 はじめに そもそもマルチモジュール化ってなんのためにするのかよくわからない なんかやらないといけないっぽいけどどこから手をつけていいのかよくわからない

Slide 5

Slide 5 text

Table of Contents 1. Pocochaの開発組織について 2. Androidアプリ開発でのマルチモジュールについて 3. PocochaのAndroidチームでのマルチモジュール化の取り組み 4. 現状の構成で困っていることと今後の展望 5

Slide 6

Slide 6 text

6

Slide 7

Slide 7 text

Pocochaの開発体制 ● 3つの機能開発チーム+基盤チーム ● Androidエンジニアだけで10名ほど 7

Slide 8

Slide 8 text

組織の拡大に伴って発生するAndroidアプリ開発上の問題 ● 他作業とのコンフリクトの多発 ● コンポーネント間の依存性の制御の難易度上昇 ● ビルド時間の伸長 8

Slide 9

Slide 9 text

Androidアプリ開発におけるマルチモジュール構成 ● Androidアプリ開発で使用するビルドシステムはGradleの上に構築されている ● Gradleとは、多言語のビルドに対応した多機能なビルドツール ○ Androidアプリのマルチモジュール構成は、Gradleの機能を使うことで実現する 説明すること 1. 特徴 2. 一般的な構成 3. メリット

Slide 10

Slide 10 text

特徴1 モジュールの構成 ● すべてのモジュールはビルドスクリプトを持っている ○ ビルドスクリプトに書かれているもの:モジュールの設定、依存関係 ○ 各モジュールで同じ外部のライブラリを使う場合はバージョンの管理に気をつける 10 Androidアプリ開発におけるマルチモジュール構成 :A :B ● 同じような性質のモジュールはビルドスクリプトの一部を共有できる ○ 共通で使う依存関係やSDKバージョンなど A/build.gradle B/build.gradle :A :B apply from: feature.gradle apply from: feature.gradle feature.gradle

Slide 11

Slide 11 text

特徴2 モジュール同士の依存性 ● 循環参照を作ることはできない 11 Androidアプリ開発におけるマルチモジュール構成 :A :B ● 一つ上の依存のみから参照できるようにするか、すべての親から参照できるようにするか選択 できる :A :B :B :C :A :B :C :A api project(“:C”) implementation project(“:C”) Cが見える Cが見えない ✅ ❌ ✅ ❌

Slide 12

Slide 12 text

特徴3 差分ビルド ● 変更が及ぼされていないモジュールはビルドしないようにできる 12 Androidアプリ開発におけるマルチモジュール構成 :A :B :C :D 変更

Slide 13

Slide 13 text

特徴3 差分ビルド ● 変更が及ぼされていないモジュールはビルドしないようにできる 13 Androidアプリ開発におけるマルチモジュール構成 :A :B :C :D 変更

Slide 14

Slide 14 text

一般的な構成 ● レイヤごとに縦に割り、機能ごとに横に割る 14 Androidアプリ開発におけるマルチモジュール構成 :app :feature:a :data :feature:b :feature:c :core UIレイヤ データレイヤ

Slide 15

Slide 15 text

一般的な構成 :core ● すべてのモジュールから参照されるモジュール ○ 文字列や画像などのリソース ○ アプリ全体から使われるデータ構造 15 Androidアプリ開発におけるマルチモジュール構成 :app :feature:a :data :feature:b :feature:c :core

Slide 16

Slide 16 text

一般的な構成 :data 16 Androidアプリ開発におけるマルチモジュール構成 ● アプリ内外からデータを取ってくる ○ APIサーバとのやり取り ○ 端末内DBを使ったキャッシュ戦略 :app :feature:a :data :feature:b :feature:c :core

Slide 17

Slide 17 text

一般的な構成 :feature 17 Androidアプリ開発におけるマルチモジュール構成 ● 具体的な画面などの機能の実装 ○ 画面ごとにモジュールを分ける :app :feature:a :data :feature:b :feature:c :core

Slide 18

Slide 18 text

一般的な構成 :app 18 Androidアプリ開発におけるマルチモジュール構成 ● アプリのエントリポイント :app :feature:a :data :feature:b :feature:c :core

Slide 19

Slide 19 text

一般的な構成→アプリによって変更する必要がある ● 例 ○ :dataモジュールが肥大化してきたらドメインごとに切る 19 Androidアプリ開発におけるマルチモジュール構成 :app :feature:a :feature:b :feature:c :core :data:d :data:e

Slide 20

Slide 20 text

マルチモジュールにする利点 ● 以上の特徴により、以下のような利点を得られる: 1. (適切なモジュール分割を行えば)責務の分離によるスケーラビリティ 2. Gradleの依存関係の制約によるアーキテクチャの強制 3. 差分ビルドによるビルド時間の短縮 20 Androidアプリ開発におけるマルチモジュール構成 (再掲)組織の拡大に伴って発生するAndroidアプリ開発上 の問題 ● 他作業とのコンフリクトの多発 ○ →適切にモジュール分割されれば、featureモジュールの変更が他のfeatureモジュールに影 響を与えることは少なくなる ● コンポーネント間の依存性の制御の難易度上昇 ○ →循環参照が作れないことにより強制的に依存性が制御できる ● ビルド時間の伸長 ● →差分ビルドによってビルド時間の短縮が望める

Slide 21

Slide 21 text

もともとマルチモジュールを意識して作られていない アプリをマルチモジュール化するのは難しい マルチモジュール化の際に障壁になったこと 1. 外部ライブラリのバージョンが直接ビルドスクリプトに書かれている 2. Applicationクラスで共有インスタンスを管理している 3. ActivityやFragmentに直接データアクセス用のロジックがかかれている 4. ActivityやFragmentを直接参照して画面遷移している 話すこと ● 問題ごとにPocochaでの対応方針を紹介 21

Slide 22

Slide 22 text

問題1 外部ライブラリのバージョンが直接ビルドスク リプトに書かれている ● すべてのモジュールで別々に外部ライブラリのアーティファクトやバージョンを書く ○ すべてのモジュールに同じことを手書きすると大きな労力がかかる ○ 手動ですべてのモジュールのライブラリバージョンアップなどはほぼ不可能 22 PocochaのAndroidチームでのマルチモジュール化の取り組み ● すべてのモジュールはビルドスクリプトを持っている ○ ビルドスクリプトに書かれているもの:モジュールの設定、依存関係 ○ 各モジュールで使う外部のライブラリのバージョンを揃える工夫が必要 :A :B A/build.gradle B/build.gradle (再掲)モジュールの構成

Slide 23

Slide 23 text

Version catalogを導入 ● Gradleで依存関係のバージョンを共有する方法はいくつか存在 ● PocochaではVersion catalogというものを使用した ● Version catalogはtoml形式でライブラリとそのバージョンの一覧を管理することができる機能 ○ バージョン番号とアーティファクトを別に管理可能 ■ 複数の関連するライブラリが同じバージョン番号でリリースされている場合に便利 ○ 複数のライブラリをまとめて使うことが可能 ■ 関連するライブラリをまとめて依存関係に追加することができる 23 PocochaのAndroidチームでのマルチモジュール化の取り組み

Slide 24

Slide 24 text

問題2 Applicationクラスで共有インスタンスを管理し ている ● Httpクライアントなど、アプリ全体で共通のインスタンスを共有したいことがある ● 共有のインスタンスがApplicationクラスで管理されていた ○ Applicationクラスは:appモジュールで定義する必要がある ● →モジュール同士が循環参照できない都合上、:dataなどからApplicationクラスに直接触れるこ とができない 24 PocochaのAndroidチームでのマルチモジュール化の取り組み :app :feature:a :data :feature:b :core ❌ インスタンス生成・管理 インスタンスを使いたい

Slide 25

Slide 25 text

DIコンテナを導入 ● DI ○ 依存性の注入パターン ○ あるオブジェクトが依存するオブジェクトを外から受 け取るようなパターン ● DIコンテナ ○ インスタンスの管理、依存性注入のための仕組みの提 供などをやってくれる ○ 他のDIコンテナが導入されていると移行の必要がある など、導入に手間がかかってしまうこともある ● 手軽さと機能性を兼ね備えたDagger Hiltを採用した 25 PocochaのAndroidチームでのマルチモジュール化の取り組み :app :feature:a :data :feature:b :core HttpClientのインスタンス インスタンスを使いたい オブジェクト

Slide 26

Slide 26 text

問題3 ActivityやFragmentに直接データアクセス用のロ ジックがかかれている ● FragmentやActivityに直接データアクセス周りのロジックがかかれているものがあった ○ データアクセス周りのロジックの例:RetrofitのServiceを呼び出して結果をResult型やリアク ティブストリームで後続の処理に伝搬させる ● レイヤードアーキテクチャの層ごとにモジュール分割することを考えると、データアクセス用 のロジックがFragmentやActivityに直接書かれているとモジュール分割できない 26 PocochaのAndroidチームでのマルチモジュール化の取り組み Fragment/Activity 通信ロジック

Slide 27

Slide 27 text

:dataを作成し、データアクセスの仕組みを隠蔽 27 PocochaのAndroidチームでのマルチモジュール化の取り組み :data:repository :data:api :data:preferences :data:local モジュール 内容 リファクタ :data:api RetrofitのService RxStreamを返却していたところ、suspend関数化 :data:preferences SharedPreference操作クラス 設定読み書きのための共通の仕組みを作成 :data:local 動画キャッシュ用の仕組み 新たに作成 :date:repository Repository 新たに作成 ● :data:repositoryより上のモジュールからは直接:data:api等を触れないようにした ○ Repositoryモジュールでデータアクセス周りの仕組みを隠蔽 :feature:a ❌ ❌ ❌

Slide 28

Slide 28 text

問題4 ActivityやFragmentを直接参照して画面遷移して いる ● 別のfeatureモジュールに入っている画面に画面遷移したい ● featureモジュールが別のfeatureモジュールに依存すると循環参照になりやすいため、そのよう な構造は避けたい ● →直接ActivityやFragmentを参照できないため、画面遷移できなくなってしまう 28 PocochaのAndroidチームでのマルチモジュール化の取り組み :feature:a :feature:b :feature:c ❌ 他のfeatureへ の依存はNG ActivityA ActivityB ActivityC 画面遷移はしたい 画面遷移はしたい ❌ 他のfeatureへ の依存はNG

Slide 29

Slide 29 text

モジュールを超えた画面遷移の仕組みについて ● Navigation Architecture Componentなどは使用せずに、機能ごとにActivityを提供している ● Activityは別のモジュールに存在するため、:featureモジュールに各機能用のルーターの抽象を用 意。:appモジュールで実装を行って、Daggerでインスタンスを注入 29 PocochaのAndroidチームでのマルチモジュール化の取り組み :app :feature:a :feature:b :feature:c interface ARouter interface BRouter interface CRouter ARouterImpl BRouterImpl CRouterImpl

Slide 30

Slide 30 text

最終的に出来上がった構成 30 :app :feature:a :data:repository :feature:b :feature:c :data:api :data:preferences :data:local :model :core PocochaのAndroidチームでのマルチモジュール化の取り組み

Slide 31

Slide 31 text

現状の構成で困っていること ● featureモジュールへの切り出しに大きな工数がかかる ○ ログイン機能の切り出しだけで3ヶ月ほどを要した ○ :dataへのデータアクセスロジックの切り出しを始めとして大きくリファクタリングする 必要があった 31 ● すでに:coreモジュールや:dataモジュールの肥大化が見られる ○ 適切な粒度での切り分けが必要

Slide 32

Slide 32 text

今後の展望 ● :coreや:data等共有のモジュールの設計見直し ○ 様々な機能を切り出すシミュレーションをし、必要なモジュールの洗い出しを行う ● 現状:appに残っている機能をマルチモジュール化するための計画 ○ 機能ごとに一度にまるごとリファクタリングしていくのは現実的ではない ○ とはいえその場しのぎを繰り返すとモジュールの責務があやふやになる ○ 何が必要なのかをよく理解し、計画的にマルチモジュール化を行う 32

Slide 33

Slide 33 text

まとめ ● マルチモジュール構成は開発組織の規模が大きくなった場合に発生する問題に対処するための 有効な手段 ● もともとマルチモジュール化することを考えて作られていないアプリをマルチモジュール化す るのは難しい ○ マルチモジュールが必要ない場合でも、本セッションで挙げた問題を意識してアプリを作 っていれば後でマルチモジュール化する工数を大きく削減できる ● マルチモジュール構成はプロダクトの性質や規模に合わせて柔軟に変える必要がある 33