Android開発でマルチモジュールを採用してみて

※ この記事はエキサイトAdvent Calendar 2018 1日目の記事です。

今年もアドベントカレンダーやりたい！ということで半ば強引にカレンダーを立ち上げた[@rmakiyama](https://twitter.com/_rmakiyama)です。
業務ではflagmeのAndroidやエキサイト婚活全般、Radiotalk(10月リリース)のAndroidを担当しています！

今回は、2年連続でAndroidのフルスクラッチ開発に関わることになり、エイヤア！と
Radiotalkアプリをマルチモジュールでの実装を進めていったお話をします。


# 設計方針

5月某日、設計方針をどうするかのチーム会議が行われました。
まずは、そこで決まった方針について簡単に説明します。

## ガンガンいこうぜ！

なかなかド新規でAndroid開発！というのは弊社では多くなく
せっかくだし使いたい技術、やりたい設計、ガンガンいこうぜ！の作戦が議決されました。

というわけで

* Kotlinは問答無用だよね
* [前回のMVVMの反省](https://qiita.com/rmakiyama/items/779cf6407f70b40e4ee7)を活かしてMVVMで！
* Repositoryパターンみなおしたい（独断と偏見）
* Instant Appと相性良さそう
* ↑もあるし完全にマルチモジュールの機運だよね
* DDDぽいの入れていきたい
* AACもやろうぜ

という欲に満ちた感じになりました。正直、反省しています。

## こうなった

![archtecture.png](https://qiita-image-store.s3.amazonaws.com/0/128802/52c3eaba-6254-4bc5-3d39-a374abdef95f.png)

* フルKotlin
* UseCaseでローカルまたはリモートからデータを取得する
* ViewModelでObservableをLiveDataに変換して扱う
* マルチモジュール
    * 別モジュールからのデータ取得はリモートから取得と同じような扱いとしClientを経由
* DBはRoomを採用
* ActivityのBaseクラスを作らずやってみよう

### モジュールの分割

モジュールの分割では「水平方向」と「垂直方向」でのわけかたがありますが、Radiotalkでは垂直方向での分割を採用しました。
水平方向での分割も同時に入れることも検討しましたが、単純にモジュール数がレイヤー数倍に膨らむといった懸念と、別プロジェクトではレイヤーわけした設計を、コードレビューを通してその原則を守るように実装できていた、などの意見から今回は採用を見送りました。

その結果、以下のような分割になりました。

* app
* common
* talk
* program
* user
* talkdetail
* programdetail
* userdetail
* record
* player
* mypage
* etc...

※ モジュールの分割は、当時もベストプラクティスがわかっておらず、１番後悔している部分です。これを参考に、はならないと思います。

### UseCase

例として、Radiotalkでトーク詳細で表示するデータを取得するUseCaseを見ていきます。（一部実コードと違う部分もアあります）

```GetDisplayedTalkUseCase.kt
internal class GetDisplayedTalkUseCase @Inject constructor(
        private val api: DetailsApiClient,
        private val programClient: ProgramClient,
        private val talkClient: TalkClient,
        private val userClient: UserClient
) : RxCommandUseCase<TalkId, DisplayedTalk>() {

    override fun execute(parameters: TalkId): Completable {
        return api.getDisplayedTalk(parameters.value)
                .flatMap { it.body()?.cache() ?: throw TalkNotFoundException(parameters) }
                .flatMapCompletable {
                    Completable.fromAction { onNextResult(it) }
                }
    }

    private fun GetDisplayedTalkResponse.cache(): Single<DisplayedTalk> {
        return Singles.zip(
                programClient.convertWithCache(program),
                talkClient.convertWithCache(talk),
                userClient.convertWithCache(user)
        ) { program, talk, user ->
            DisplayedTalk(program, talk, user, clipped)
        }
    }
}
```

このように`Program`, `Talk`, `user`それぞれのコンバートやキャッシュ処理は、各モジュールで行うため、`Client`を経由して処理をしています。

# 所感

## よかったこと

### 責務の分離ができた

Kotlinの`internal`修飾子を使うことで、制約を強くできたのは大きかったです。あとは、コードレビューで、「これはここにかくべきではない」「こっちのモジュールの責務では」みたいな議論ができたのも良かったです！

### 機能開発の分担が楽だった

リリースまでの開発は２名で行いました。当時、お互いに主務は別にあったため、手の空いたときにという状況でした。
今回は機能ごとにモジュールを分けていたので、自分の開発に集中することができました。

## つらかったこと

開発期間が短い中、かなり手探りだったのでたくさんありました…
ざっと列挙すると

* 循環参照
    * 画面遷移の循環
    * Responseのモデルを使い回せない
* DI（Dagger）
    * 雰囲気でDIを使っている
    * マルチモジュールでのDIの知見が少ない
* ボイラーテンプレートが多い
    * モジュール毎にDaggerの諸々を書かなきゃ（仕方ないよね）
    * 毎回、DataBindingとViewModelの初期化とか
        * Baseクラスを導入しても良いかも

とくに困った循環参照問題について特筆してみます。

### 循環参照

マルチモジュールならではの問題で、モジュールAがモジュールBに依存している場合、モジュールBはモジュールAに依存できません。
ここで画面遷移の循環が発生します。例えば、Radiotalkの場合、番組詳細からトーク画面へ、トーク画面から番組詳細への画面遷移があります。

![スクリーンショット 2018-12-01 17.21.27.png](https://qiita-image-store.s3.amazonaws.com/0/128802/b96133c1-b69f-a4fa-dcf9-05e8e183995f.png)

当初、それぞれ`programdetail`/`talkdetail`のモジュールに分けていたため、循環依存が発生していました。

悩んだ結果、この場合に関しては、「詳細を表示する」という意味合いのモジュールとして**モジュールの統合**をして解決しました。
DIを用いてインターフェースを使って、という方法も検討しましたが、開発期間の問題もあり、スピード重視の判断だったので最善ではなさそうです。

## わからないこと

本業もありながら１０月のリリースに向けてスピード重視という部分もあり、不安も疑問も抱えながらの実装でした。
徐々にマルチモジュールの知見も多く見られるようになった今、まだ悩んでいる、わからない部分も多々あります。
    
* マルチモジュール開発にしたけどビルドかなり遅い
    * 特にcommonを変えたとき。そんなものなのかな。設定の問題、？
* APIモジュールを作るべきだったのか
    * 分割の考え方にもよるけど、作っておくとスムーズだったかも
* ioschedくらい少ない分割でも良かったかも。
    * 開発メンバーも多くない中で、分割数も結構あり、制約が多すぎたかもなと思っている

# 今後

リリースも完了した今、大きくやりたいことは以下の２つです。

* モジュールの整理
* DI整理

今回は、大げさに言うと「画面単位」くらいのモジュール分割になっています。
ここはサービスを俯瞰して、しっかりしたコンテキストマップを作り、モジュールの整理をしたいです。

DIについても、雰囲気でDaggerを使っている感じが否めないので、スコープを意識して設計しなおしたいです。

# 参考

* [multi-module-no-susume](https://speakerdeck.com/kgmyshin/multi-module-no-susume)
* [大きめなAndroidアプリでの設計を考えてみる](https://booth.pm/ja/items/837226)
* [Android アプリ設計パターン入門](https://peaks.cc/books/architecture_patterns)
* [EightにおけるAndroidのリアーキテクチャ/Android's re-architecture at Eight](https://speakerdeck.com/sansanbuildersbox/androids-re-architecture-at-eight)