前回の記事(マルチモジュールにして生産性を上げよう)の続きです。
筆者がマルチモジュール化を通して得た知見を共有します。
#モジュール構成
まずはどんな感じのアーキテクチャにしたのかをご紹介します。
筆者の担当プロダクトは、レイヤードアーキテクチャだったので、以下の様な構造になりました。
ルール概要
- bridge_diは全てのモジュールから参照OK
- 上位レイヤーから下位レイヤーへの参照はOKだが、逆は禁止(componentが上位、infraが下位)
- 同一レイヤー内では、coreモジュールは参照OK。その他は兄弟間の参照も禁止。(xxxモジュールは、coreは参照OKだが、yyyは参照NG)
- coreモジュールは、基本的には共通コードのみ配置する思想。しかし、レガシーコードの参照が複雑で切り出しに工数がかかる場合は、全てcoreに突っ込む。
bridge_di
全レイヤー、全モジュールから参照可能。DI(Dependendy Injection)を行うためのフレームワークで、かなり抽象度が高い実装になっています。
infra層
通信リポジトリ、DB、ストレージ操作などの実装クラス群を配置しています。
domain層
ビジネスロジックを集約しました。
component層
UI周りの実装(Activity, Fragmentなど)と、その他、レガシーコードで参照が複雑過ぎて切り出せないコードを全て突っ込んでいます。
#マルチモジュール移行のコツ
###モジュール構成を決め、既存クラスを移動する(ビルドエラーは放置)
空のモジュール、空のパッケージを作成し、ひたすらドラッグ&ドロップします。
(自信のある人はリファクタリング機能を使っても良いかもですが、筆者は予期せぬ自動リファクタで何度も手戻りが発生。。。)
目的のモジュールにクラスが一通り揃ったら、ビルドが通ってなくても一旦、ローカルリポジトリにコミットします。
クラスの移動とビルドエラー修正を同時に行ってしまうと、ビルドエラーの修正内容や、マルチモジュールに適合するための実装が埋もれてしまいます(実装トレーサビリティ低下)。
##徹底的にDIに頼ってビルドエラー修正(依存関係を解消)していく
前述の通り、モジュール間の参照ルールが非常に厳しいので、多少汚いコードでも良いので、まずはDIを徹底的に活用してビルドエラーを解決していきます。
筆者の場合、Dagger2を使っていますが、appモジュールにDagger系の実装を詰め込みました。
interfaceは、infra_coreに配置しました。
##uiは一旦同じモジュールに閉じ込める
uiは様々な依存関係を持ち、カオスになりがちです。ここを理想的な設計にするのはかなり大変。
まずは、uiをcomponent_coreに閉じ込めましょう。fatモジュール上等です。
リファクタリングは、今じゃなくても出来ます。
##共通で外部参照しているライブラリを、徹底的に集約する。
domain_xxxとcomponento_yyyで共通の外部参照ライブラリを参照しているような場合
例えば、infra_coreに外部参照ライブラリのwrapperを作成し、domain層、component層に提供します。
そうする事で、gradleに記載する外部参照ライブラリがinfra層のみに集約され、ビルド時間が僅かに短くなります。
##gradleは徹底してimplement。api参照は禁止。
api参照はとても便利ですよね。しかし、ビルド時間を少しでも短くする為には、徹底してimplementを使います。
例えば、infra_core、component_xxxで同じ外部参照を用いる場合、infra層でapi参照すれば、component層でも依存関係が解消されます。
しかし、component層を修正した際、外部参照ライブラリを探してinfra層まで増分ビルドが発生してビルド時間が短縮されません。
##Butter Knifeは作法変更が必要
こちらを参照。
R2クラスを使うことになります。
##java & kotlinが混在するプロジェクトは、kaptのビルドエラー注意
Javaコードの方には適切に依存関係を指定してても、最終的にappモジュールでリンクする際に、kaptでエラーが発生する事があります。
原因を追求するのが望ましいですが、あまり時間がかかるようならば、appモジュールのgradleにビルドエラーが発生するリンク切れをkaptに指定してやりましょう。
##多少の変なコードは一旦、容認する
ここまで読んでいただいた方は、感じているかもしれません。
「本当にこんな事できるの??メチャクチャになりそうだけど」
はい、抽象度が非常に高いクラスが発生したり、汎用的ではないDIの実装が発生したりすると思います。
ですが、多少の変なコードを許容する勇気も必要と考えています。
いきなり完璧を求めすぎず、別のタイミングでリファクタリングすれば良いんじゃないでしょうか。
#いかがでしたでしょうか
マルチモジュール化すると、依存関係の制約が厳しくなり、実装が面倒になるかと思いきや、モジュール毎に担当範囲が明確になるので、設計に秩序が発生し、楽になったりします。
あとは、モジュール毎にテストを書くと、差分ビルドがそのモジュールのみに限定されるのでビルド時間が早くなったり、メリットも多いです。
設計やビルドにかかっていた時間を短縮し、浮いた時間でコーヒーブレイクしてみてはどうでしょう。
忙しい日々に、少し、ゆとりができるかもしれませんね。