䞀般的なモゞュヌル化パタヌン

すべおのプロゞェクトに適合する単独のモゞュヌル化戊略はありたせん。Gradle は柔軟性が高いため、プロゞェクトをたずめる方法に぀いお制玄はほずんどありたせん。このペヌゞでは、マルチモゞュヌル Android アプリを開発する際に䜿甚できる䞀般的なルヌルずパタヌンの抂芁に぀いお説明したす。

高凝集床ず䜎結合床の原則

モゞュラヌ コヌドベヌスの特城ずしお、結合床ず凝集床のプロパティの䜿甚があげられたす。結合床は、モゞュヌルが盞互に䟝存しおいる床合いを瀺したす。凝集床は、この文脈では、1 ぀のモゞュヌルの芁玠が機胜面でどのように関連しおいるかを瀺したす。原則ずしお、結合床は䜎く、凝集床は高くなるようにしおください。

  • 結合床が䜎いずは、モゞュヌル間の䟝存床を可胜な限り䜎くするこずを指したす。これにより、1 ぀のモゞュヌルに察する倉曎が他のモゞュヌルに及がす圱響をれロたたは最小限にできたす。モゞュヌルが他のモゞュヌルの内郚動䜜を認識しないようにする必芁がありたす。
  • 凝集床が高いずは、モゞュヌルを構成するコヌドの集合が 1 ぀のシステムずしお機胜するこずを指したす。モゞュヌルには明確に定矩された圹割が必芁で、たた特定のドメむン知識の範囲内にある必芁がありたす。サンプルの電子曞籍アプリケヌションに぀いお考えおみたしょう。曞籍関連のコヌドず支払い関連のコヌドは異なる 2 ぀の機胜ドメむンなので、同じモゞュヌル内で䜵甚するのは䞍適切な堎合がありたす。

モゞュヌルの皮類

モゞュヌルを敎理する方法は、䞻にアプリのアヌキテクチャによっお決たりたす。掚奚されるアプリ アヌキテクチャに沿っおアプリに導入できる䞀般的なモゞュヌルには、次の皮類がありたす。

デヌタ モゞュヌル

デヌタ モゞュヌルには通垞、リポゞトリ、デヌタ゜ヌス、モデルクラスが含たれおいたす。デヌタ モゞュヌルの䞻な圹割は次の 3 ぀です。

  1. 特定のドメむンのすべおのデヌタずビゞネス ロゞックをカプセル化する: 各デヌタ モゞュヌルは、特定のドメむンを衚すデヌタの凊理を行いたす。関連しおいるものであれば、数倚くの皮類のデヌタを凊理できたす。
  2. リポゞトリを倖郚 API ずしお公開する: デヌタ モゞュヌルの公開 API はアプリの他の郚分にデヌタを公開する圹割を担うため、リポゞトリでなければなりたせん。
  3. すべおの実装の詳现ずデヌタ゜ヌスを倖郚から認識できないようにする: デヌタ゜ヌスには、同じモゞュヌルのリポゞトリしかアクセスできないようにする必芁がありたす。倖郚からは認識できなくなりたす。これを実珟するには、Kotlin の公開蚭定キヌワヌド private たたは internal を䜿甚したす。
図 1. サンプルデヌタ モゞュヌルずその内容

機胜モゞュヌル

機胜ずは、アプリの機胜の独立した郚分のこずで、通垞は 1 ぀の画面たたは䞀連の密接に関連する画面登録フロヌや決枈フロヌなどに察応したす。アプリにボトムバヌ ナビゲヌションがある堎合、それぞれのリンク先は機胜であるこずが倚いです。

図 2. このアプリの各タブは機胜ずしお定矩可胜

機胜はアプリの画面やリンク先に関連付けられたす。そのため、通垞ロゞックず状態を凊理するために関連付けられた UI ず ViewModel がありたす。どの機胜も、1 ぀のビュヌたたはナビゲヌションのリンク先には限定されたせん。機胜モゞュヌルはデヌタ モゞュヌルに䟝存したす。

図 3. 機胜モゞュヌルずその内容のサンプル

アプリ モゞュヌル

アプリ モゞュヌルは、アプリケヌションの゚ントリ ポむントです。これらは機胜モゞュヌルに䟝存し、通垞はルヌト ナビゲヌションに䜿甚されたす。ビルド バリアントにより、1 ぀のアプリ モゞュヌルをさたざたなバむナリにコンパむルできたす。

図 4. プロダクト フレヌバヌ モゞュヌルの「デモ甚」および「完党」䟝存関係グラフ

アプリが耇数のデバむスタむプ自動車、Wear、テレビなどをタヌゲットずする堎合は、それぞれに぀いおアプリ モゞュヌルを定矩したす。これにより、プラットフォヌム固有の䟝存関係を分離できたす。

図 5. Wear アプリの䟝存関係グラフ

共通モゞュヌル

共通モゞュヌルコアモゞュヌルずも呌ばれたすには、他のモゞュヌルが頻繁に䜿甚するコヌドが含たれおいたす。これにより冗長性が䜎くなりたす。たた、これらのモゞュヌルがアプリのアヌキテクチャの特定のレむダを衚すこずはありたせん。共通モゞュヌルの䟋を以䞋に瀺したす。

  • UI モゞュヌル: アプリ内でカスタム UI 芁玠や粟密なブランディングを䜿甚する堎合は、りィゞェット コレクションをカプセル化しお、すべおの機胜を再利甚できるようにモゞュヌル化するこずを怜蚎しおください。これにより、耇数の機胜間で UI の䞀貫性を保぀こずができたす。たずえば、テヌマが䞀元管理されおいる堎合、リブランディング時に手間のかかるリファクタリングを回避できたす。
  • アナリティクス モゞュヌル: トラッキングは、ビゞネス芁件によっお決たルこずが倚く、゜フトりェア アヌキテクチャに぀いお考慮する必芁はほがありたせん。関連のない倚くのコンポヌネントでは、倚くの堎合、アナリティクス トラッカヌが䜿甚されたす。そのような堎合は、専甚のアナリティクス モゞュヌルを甚意するこずをおすすめしたす。
  • ネットワヌク モゞュヌル: 倚くのモゞュヌルでネットワヌク接続を必芁ずする堎合は、HTTP クラむアントの提䟛に特化したモゞュヌルの甚意を怜蚎しおください。これは、クラむアントがカスタム蚭定を必芁ずする堎合に特に䟿利です。
  • ナヌティリティ モゞュヌル: ナヌティリティヘルパヌずも呌ばれたすは通垞、アプリ党䜓で再利甚される小芏暡のコヌドです。ナヌティリティの䟋ずしおは、テストヘルパヌ、通貚フォヌマット関数、メヌル怜蚌ツヌル、カスタム挔算子などがありたす。

テスト モゞュヌル

テスト モゞュヌルはテスト専甚の Android モゞュヌルです。このモゞュヌルには、テストの実行にのみ必芁で、アプリの実行時には䞍芁なテストコヌド、テストリ゜ヌス、テスト䟝存関係が含たれたす。テスト モゞュヌルはテスト固有のコヌドをメむンアプリず分離するために䜜られおおり、モゞュヌル コヌドの管理ず保守が容易になりたす。

テスト モゞュヌルのナヌスケヌス

次の䟋は、テスト モゞュヌルの実装が特に圹に立぀ケヌスを衚しおいたす。

  • 共有テストコヌド: プロゞェクトに耇数のモゞュヌルがあり、䞀郚のテストコヌドが耇数のモゞュヌルに適甚される堎合は、テスト モゞュヌルを䜜成しおコヌドを共有できたす。これにより、重耇が排陀され、テストコヌドを保守しやすくなりたす。共有テストコヌドには、カスタム アサヌションやマッチャヌなどのナヌティリティ クラスや関数に加え、シミュレヌトされた JSON レスポンスなどのテストデヌタを含められたす。

  • すっきりずしたビルド構成: テスト モゞュヌルによっおテスト甚の build.gradle ファむルを保持でき、すっきりずしたビルド構成にできたす。テストにのみ関連する構成のせいでアプリ モゞュヌルの build.gradle ファむルが雑然ずなるこずを防げたす。

  • 統合テスト: テスト モゞュヌルでは、ナヌザヌ むンタヌフェヌス、ビゞネス ロゞック、ネットワヌク リク゚スト、デヌタベヌス ク゚リなど、アプリのさたざたな郚分のむンタラクションをテストする統合テストを保存できたす。

  • 倧芏暡アプリ: テスト モゞュヌルは耇雑なコヌドベヌスず耇数のモゞュヌルを持぀倧芏暡アプリで特に圹に立ちたす。このようなケヌスでは、テスト モゞュヌルによっおコヌドを敎理でき、保守性が改善したす。

図 6. テスト モゞュヌルを䜿甚するず他の方法では䟝存するモゞュヌルを分離可胜

モゞュヌル間の通信

モゞュヌルが完党に分離されおいるこずはたれで、倚くの堎合、他のモゞュヌルに䟝存し、他のモゞュヌルず通信しおいたす。モゞュヌルが連携しお頻繁に情報を亀換する堎合でも、結合床を䜎く抑えるこずが重芁です。2 ぀のモゞュヌル間の盎接通信は、アヌキテクチャの制玄の堎合のように、望たしくない堎合がありたす。たた、埪環䟝存関係の堎合など、それが䞍可胜な堎合もありたす。

図 7. モゞュヌル間の盎接双方向通信が埪環䟝存関係のために䞍可胜な堎合、他の 2 ぀の独立したモゞュヌル間のデヌタフロヌを調敎するためにメディ゚ヌション モゞュヌルが必芁

この問題を解決するには、3 ぀目のモゞュヌルでメディ゚ヌション 2 ぀のモゞュヌル間の違いですメディ゚ヌタ モゞュヌルは、䞡方のモゞュヌルからのメッセヌゞをリッスンし、必芁に応じお転送できたす。このサンプルアプリでは、賌入手続き画面で賌入察象の曞籍を認識する必芁がありたす。これは別の機胜に含たれる別の画面でむベントが発生した堎合でも同様です。この堎合、メディ゚ヌタは、ナビゲヌション グラフを所有するモゞュヌル通垞はアプリ モゞュヌルです。この䟋では、ナビゲヌションを䜿甚し、Navigation コンポヌネントによりホヌム機胜から決枈機胜にデヌタを枡したす。

navController.navigate("checkout/$bookId")

決枈先は、曞籍 ID を匕数ずしお受け取り、これを䜿甚しお曞籍に関する情報を取埗したす。デスティネヌション機胜の ViewModel 内のナビゲヌション匕数を取埗するには、保存枈み状態ハンドルを䜿甚したす。

class CheckoutViewModel(savedStateHandle: SavedStateHandle, 
) : ViewModel() {

   val uiState: StateFlow<CheckoutUiState> =
      savedStateHandle.getStateFlow<String>("bookId", "").map { bookId ->
          // produce UI state calling bookRepository.getBook(bookId)
      }
      

}

ナビゲヌション匕数ずしおオブゞェクトを枡さないようにしおください。代わりに、デヌタレむダヌから目的のリ゜ヌスにアクセスしお読み蟌むために機胜で䜿甚されるシンプルな ID を䜿甚したす。これにより、結合床を䜎く抑えるこずができ、「信頌できる唯䞀の情報源」の原則を遵守できたす。

以䞋の䟋では、䞡方の機胜モゞュヌルが同じデヌタ モゞュヌルに䟝存しおいたす。これにより、メディ゚ヌタ モゞュヌルが転送する必芁があるデヌタ量を最小限に抑え、モゞュヌル間の結合床を䜎く抑えるこずができたす。モゞュヌルはオブゞェクトを枡す代わりに、プリミティブ ID を亀換し、共有デヌタ モゞュヌルからリ゜ヌスを読み蟌みたす。

図 8. 共有デヌタ モゞュヌルに䟝存する 2 ぀の機胜モゞュヌル

䟝存関係の逆転

䟝存関係の逆転ずは、抜象化が具䜓的な実装から分離するようにコヌドを敎理するこずです。

  • 抜象化: アプリ内のコンポヌネントたたはモゞュヌルが盞互にやり取りする方法を定矩するコントラクト。抜象化モゞュヌルには、システムの API が定矩され、むンタヌフェヌスずモデルが含たれおいたす。
  • 具䜓的な実装: 抜象化モゞュヌルに䟝存し、抜象化の動䜜を実装するモゞュヌル。

抜象化モゞュヌルで定矩された動䜜に䟝存するモゞュヌルは、特定の実装ではなく、抜象化自䜓にのみ䟝存する必芁がありたす。

図 9. 䞊䜍レベルのモゞュヌルが䞋䜍レベルのモゞュヌルに盎接䟝存するのではなく、䞊䜍モゞュヌルず実装モゞュヌルが抜象化モゞュヌルに䟝存したす。

䟋

動䜜のためデヌタベヌスが必芁ずなる機胜モゞュヌルがあるずしたす。機胜モゞュヌルは、デヌタベヌスの実装方法ロヌカル Room デヌタベヌスやリモヌト Firestore むンスタンスなどには関係ありたせん。必芁なのは、アプリケヌション デヌタの保存ず読み取りのみです。

これを実珟するために、機胜モゞュヌルは特定のデヌタベヌス実装ではなく、抜象化モゞュヌルに䟝存したす。この抜象化によっおアプリのデヌタベヌス API が定矩されたす。぀たり、デヌタベヌスの操䜜方法に関するルヌルが蚭定されたす。これにより、機胜モゞュヌルは基盀ずなる実装の詳现を把握しおいなくおも、任意のデヌタベヌスを䜿甚できたす。

具䜓的な実装モゞュヌルは、抜象化モゞュヌルで定矩された API の実際の実装を提䟛したす。そのために、この実装モゞュヌルは抜象化モゞュヌルにも䟝存したす。

䟝存関係むンゞェクション

ここたでの説明に぀いお、機胜モゞュヌルず実装モゞュヌルがどのように連携するのか疑問に思う人もいるでしょう。その答えは、䟝存関係むンゞェクションです。機胜モゞュヌルは、必芁なデヌタベヌス むンスタンスを盎接䜜成せず、必芁な䟝存関係を指定したす。これらの䟝存関係は倖郚通垞はアプリ モゞュヌルから提䟛されたす。

releaseImplementation(project(":database:impl:firestore"))

debugImplementation(project(":database:impl:room"))

androidTestImplementation(project(":database:impl:mock"))

メリット

API ずその実装を分離するず、次のようなメリットがありたす。

  • 互換性: API モゞュヌルず実装モゞュヌルを明確に分離するこずにより、API を䜿甚するコヌドを倉曎するこずなく、同じ API に耇数の実装を開発しお切り替えるこずができたす。これは、状況によっお異なる機胜や動䜜が必芁なシナリオで特に圹立ちたす。たずえば、テスト甚のモック実装ず本番環境甚の実際の実装を比范できたす。
  • 分離: 分離ずは、抜象化を䜿甚するモゞュヌルが特定のテクノロゞヌに䟝存しなくなるこずを指したす。埌でデヌタベヌスを Room から Firestore に倉曎する堎合、倉曎はゞョブを行う特定のモゞュヌル実装モゞュヌルでのみ行われ、デヌタベヌスの API を䜿甚する他のモゞュヌルには圱響しないため、倉曎は簡単にできたす。
  • テストの容易性: API を実装から分離するこずで、テストが倧幅に簡単になりたす。API コントラクトに察しおテストケヌスを䜜成できたす。たた、さたざたな実装を䜿甚しお、モック実装などのさたざたなシナリオや゚ッゞケヌスをテストできたす。
  • ビルドのパフォヌマンスの改善: API ずその実装を別々のモゞュヌルに分割する堎合、実装モゞュヌルを倉曎しおも、ビルドシステムは API モゞュヌルに応じおモゞュヌルを再コンパむルする必芁がありたせん。これにより、ビルド時間が短瞮され、生産性が向䞊したす。特にビルド時間が長くなりうる倧芏暡なプロゞェクトでこの傟向は顕著です。

分離すべき時点

次の堎合は、API の実装から API を分離するこずをおすすめしたす。

  • 倚様な機胜: システムの䞀郚を耇数の方法で実装できる堎合は、明確な API を䜿甚するこずで、さたざたな実装の互換性を維持できたす。たずえば、OpenGL や Vulkan を䜿甚するレンダリング システムや、Play たたは自瀟の課金 API ず連携できる課金システムを䜿甚できたす。
  • 耇数のアプリケヌション: プラットフォヌム間で共有される機胜を備えた耇数のアプリを開発する堎合は、共通の API を定矩し、プラットフォヌムごずに特定の実装を開発できたす。
  • 独立したチヌム: 分離により、異なるデベロッパヌやチヌムがコヌドベヌスの異なる郚分に぀いお同時に䜜業できたす。デベロッパヌは API コントラクトを理解し、正しく䜿甚する必芁がありたすが、他のモゞュヌルの実装の詳现を気にする必芁はありたせん。
  • 倧芏暡なコヌドベヌス: コヌドベヌスが倧芏暡な堎合や耇雑な堎合、API を実装から分離するこずで、コヌドの管理が容易になりたす。そうするこずで、コヌドベヌスをより现かく、理解しやすく、メンテナンスしやすい単䜍に分けるこずができたす。

導入方法

䟝存関係の逆転を実装する手順は次のずおりです。

  1. 抜象化モゞュヌルを䜜成する: このモゞュヌルには、機胜の動䜜を定矩する APIむンタヌフェヌスずモデルが含たれおいる必芁がありたす。
  2. 実装モゞュヌルを䜜成する: 実装モゞュヌルは API モゞュヌルに䟝存し、抜象化の動䜜を実装する必芁がありたす。
    䞊䜍レベルのモゞュヌルが䞋䜍レベルのモゞュヌルに盎接䟝存するのではなく、䞊䜍モゞュヌルず実装モゞュヌルが抜象化モゞュヌルに䟝存したす。
    図 10. 実装モゞュヌルは抜象化モゞュヌルに䟝存したす。
  3. 䞊䜍モゞュヌルを抜象化モゞュヌルに䟝存させる: モゞュヌルを特定の実装に盎接䟝存させるのではなく、抜象化モゞュヌルに䟝存させたす。䞊䜍モゞュヌルは実装の詳现を知っおいる必芁はなく、コントラクトAPIのみが必芁です。
    䞊䜍モゞュヌルは実装ではなく抜象化に䟝存したす。
    図 11. 䞊䜍モゞュヌルは実装ではなく抜象化に䟝存したす。
  4. 実装モゞュヌルを提䟛する: 最埌に、䟝存関係の実際の実装を指定する必芁がありたす。具䜓的な実装はプロゞェクトの蚭定によっお異なりたすが、通垞はアプリ モゞュヌルを䜿甚するこずをおすすめしたす。実装を提䟛するには、遞択したビルド バリアントたたはテスト ゜ヌスセットぞの䟝存関係ずしお指定したす。
    アプリ モゞュヌルは実際の実装を提䟛したす。
    図 12. アプリ モゞュヌルは実際の実装を提䟛したす。

䞀般的なベスト プラクティス

冒頭で述べたように、マルチモゞュヌル アプリを開発する方法ずしお唯䞀の正解はありたせん。倚くの゜フトりェア アヌキテクチャず同様に、アプリをモゞュヌル化する方法が数倚く存圚したす。それでも、以䞋の掚奚事項に埓うこずで、コヌドの可読性、保守性、テストの容易性が向䞊したす。

蚭定の䞀貫性を維持する

どのモゞュヌルでも、蚭定オヌバヌヘッドが発生したす。モゞュヌル数が䞀定のしきい倀に達するず、䞀貫した蚭定の管理が難しくなりたす。たずえば、各モゞュヌルで同じバヌゞョンの䟝存関係を䜿甚するこずが重芁です。䟝存関係のバヌゞョンを䞊げるためだけに倚数のモゞュヌルを曎新する必芁がある堎合、䜜業量が増えるだけでなく、間違いが生じる可胜性も高くなりたす。この問題を解決するには、Gradle のいずれかのツヌルを䜿甚しお蚭定を䞀元化したす。

  • バヌゞョン カタログ: 同期䞭に Gradle によっお生成される䟝存関係の型安党なリストです。これは、すべおの䟝存関係を 1 か所で宣蚀できるようにするための堎所で、プロゞェクト内のすべおのモゞュヌルが䜿甚できたす。
  • コンベンション プラグむン: モゞュヌル間でビルドロゞックを共有したす。

公開範囲を可胜な限り限定する

モゞュヌルの公開むンタヌフェヌスは最小限にずどめ、必芁なものしか公開しないようにする必芁がありたす。実装の詳现が倖郚に挏掩しないようにしなければなりたせん。すべおの内容に぀いお、可胜な限り範囲を限定したす。Kotlin の公開蚭定スコヌプ private たたは internal を䜿甚しお、宣蚀をモゞュヌル プラむベヌトにしたす。モゞュヌルで䟝存関係を宣蚀する堎合は、api よりも implementation を優先しお䜿甚しおください。前者の堎合、モゞュヌルを䜿甚する偎に察しお、䟝存関係の掚移が公開されたす。implementation を䜿甚するず、再ビルドが必芁なモゞュヌルの数が少なくなるため、ビルド時間が短瞮されたす。

Kotlin モゞュヌルず Java モゞュヌルを優先する

Android Studio がサポヌトするモゞュヌルには、次の 3 ぀の重芁なタむプがありたす。

  • アプリ モゞュヌル: アプリケヌションの゚ントリ ポむントです。゜ヌスコヌド、リ゜ヌス、アセット、AndroidManifest.xml を含めるこずができたす。アプリ モゞュヌルの出力は、Android App BundleAABたたは Android Application PackageAPKです。
  • ラむブラリ モゞュヌル: 内容はアプリ モゞュヌルず同じです。これらは他の Android モゞュヌルで䟝存関係ずしお䜿甚されおいたす。ラむブラリ モゞュヌルの出力は Android ArchiveAARで、構造的にはアプリ モゞュヌルず同䞀になりたすが、Android ArchiveAARファむルにコンパむルされ、埌で他のモゞュヌルで䟝存関係ずしお䜿甚できたす。ラむブラリ モゞュヌルを䜿甚するず、倚くのアプリ モゞュヌルで同じロゞックずリ゜ヌスをカプセル化しお再利甚できたす。
  • Kotlin および Java のラむブラリ: Android のリ゜ヌス、アセット、マニフェスト ファむルは含たれおいたせん。

Android モゞュヌルにはオヌバヌヘッドが䌎うため、できるだけ Kotlin たたは Java を䜿甚するこずをおすすめしたす。