モジュール性とは、アプリケーションを独立したモジュールから組み立て、各モジュールが1つの機能を担当するという原則です。Android Developersによると、モジュールに分割することで並列コンパイルによるビルドの高速化と、チームがアプリケーションの異なる部分を独立して作業できるようになります。モジュラーアーキテクチャは、数十人の開発者がいる大規模なモバイルプロジェクトの標準となっています。
主要ポイント
モジュール性とは、アプリケーションが疎結合のモジュールで構成され、各モジュールが公開インターフェースを通じて厳密に定義された機能を提供するコード構成方法です。すべてのクラスが単一のプロジェクトに存在するモノリシックアーキテクチャとは異なり、モジュラーアプローチはコードを物理的に独立したビルド単位に分割します。
モジュール性の主な目的は複雑性の管理です。開発者はコードベース全体を意識することなく、1つのモジュールに集中できます。各モジュールには独自の責任範囲があり、他のモジュールから独立して開発、テスト、デプロイできます。これは10人以上の開発者がいるプロジェクトで特に価値があり、モノリスでの並行作業は頻繁なマージ競合を引き起こします。
モジュール性とレイヤードアーキテクチャを区別することが重要です。レイヤー(Presentation、Domain、Data)は技術的な基準でコードを分割しますが、モジュールは機能的な基準で分割します。「ユーザープロファイル」モジュールは内部に独自のレイヤーを持つことができます。実際には、モジュラーアプローチとレイヤードアーキテクチャは組み合わされ、各モジュールは独自の3層構造を持ちます。
フィーチャーモジュールは最も一般的なモジュールタイプです。各画面または関連する画面のグループは、Onboarding、Profile、Settings、Feedなどの独自のモジュールに分離されます。フィーチャーモジュールには、UI、ビジネスロジック、データ層など、機能の動作に必要なすべてが含まれます。モジュールの境界は保護されており、他の機能は内部クラスにアクセスできません。
コアモジュールには、ネットワーキング、データベース、分析、デザインシステムなどの共通インフラストラクチャが含まれます。これらはフィーチャーモジュールに依存しませんが、フィーチャーモジュールはこれらに依存します。この分離により、分析SDKの変更がネットワーキング層に影響を与えず、その逆もないことが保証されます。コアモジュールはコードの重複なく機能間で再利用されます。
共有モジュールには、複数の機能で使用されるコード(データモデル、ユーティリティ、定数、カスタムビュー)が含まれます。共有モジュールの主な問題は、異種コードが時間とともに蓄積される「雑多モジュール」と化すリスクです。ルール:共有モジュールには明確なテーマが必要です。たとえば「shared-ui」や「shared-models」などです。
Androidでは、共有モジュールはlib接頭辞付きのライブラリに分離されることがよくあります:lib-network、lib-database、lib-ui-components。iOSでは、Workspace内の内部Swift Packagesが同じ機能を果たします。実際には、チームはビルドを複雑にする過剰な依存関係ネットワークを避けるため、共有モジュールの数を3~5に制限しています。
個別のテストモジュールにより、テストスイート全体を実行せずに変更されたモジュールのみのテストを実行できます。これによりCI/CDパイプラインの時間が数時間から数分に短縮されます。モジュールレベルの分離はビルドレベルでのSoCを保証します。ネットワーキング層モジュールはテストで誤ってUIライブラリをインポートすることができません。
各モジュールには明確に定義された公開APIが必要です。Androidでは、アクセス修飾子とGradleのapi対implementationによって実現されます。iOSでは、public/internalアクセス修飾子とPackage.swiftを介した管理された依存関係によって実現されます。可視性を必要最小限に減らすことがモジュラーデザインの主要なプラクティスです。
Gradleはモジュラーアーキテクチャをネイティブでサポートしています。各モジュールは独自のbuild.gradleファイルを持つ個別のビルド単位です。Androidプロジェクトは、アプリケーションモジュール(app)と複数のライブラリモジュールの組み合わせを使用します。ライブラリモジュールはアプリケーションとして実行できませんが、リポジトリにAARとして公開できます。
Gradleの重要な機能は、独立したモジュールの並列ビルドです。モジュールA、B、Cが互いに依存していない場合、GradleはすべてのCPUコアを使用して同時にコンパイルします。20以上のモジュールがあるプロジェクトでは、フルビルドが15分から3~5分に短縮されます。変更されたモジュールのインクリメンタルビルドは数秒です。
Gradleはモジュール間の2つの依存関係タイプを提供します:api(推移的)とimplementation(非推移的)。この違いはモジュール性にとって極めて重要です。implementationはモジュールの消費者から推移的依存関係を隠します。:profileモジュールがimplementationを介して:networkingを使用する場合、:profileの消費者は:networkingを知らず、アクセスできません。
// settings.gradle — モジュール宣言
include ':app'
include ':feature:profile'
include ':feature:settings'
include ':core:network'
include ':core:database'
// build.gradle feature/profile — モジュール依存関係
dependencies {
implementation project(':core:network')
implementation project(':core:database')
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.8.0'
}
コードはモジュラーAndroidプロジェクトの構造を示しています。Settings.gradleはすべてのモジュールをリストし、各フィーチャーモジュールのbuild.gradleは必要なコアモジュールのみを指定します。ビルドシステムは推移的依存関係を自動的に解決し、正しい順序でモジュールをビルドします。
Swift Package Manager(SPM)は2019年からiOSにおける標準のモジュール性ツールです。SPMはアプリケーションをSwift Packagesに分割でき、各パッケージはライブラリまたは実行可能ファイルになります。PackageはPackage.swiftを介してモジュール(targets)とその依存関係を定義します。SPMはXcodeに統合されており、追加のツールは必要ありません。
CocoaPodsはサードパーティライブラリの主要な依存関係マネージャーであり続けています。PodfileとPodspecがモジュール構造を定義し、CocoaPodsは個別のpodプロジェクトを含むワークスペースを生成します。プロジェクト独自のモジュール性については、チームはXcodeに組み込まれていてインストール不要のSPMを選ぶ傾向が強まっています。
iOSのモジュール性では、アクセス制御が重要な役割を果たします:public、package、internal、fileprivate、private。モジュールは他のモジュールがアクセスすべき型のみを公開します。内部実装の詳細はinternalおよびprivate修飾子の背後に隠されます。これによりモジュール間の隠れた依存関係を防ぎます。
// Package.swift — iOSプロジェクトのモジュール構造
let package = Package(
name: "MyApp",
platforms: [.iOS(SupportedPlatform.iOSVersion.v17)],
products: [
.library(name: "ProfileFeature", targets: ["ProfileFeature"]),
.library(name: "NetworkCore", targets: ["NetworkCore"]),
],
dependencies: [
.package(url: "https://github.com/Alamofire/Alamofire.git", from: "5.9.0")
],
targets: [
.target(name: "ProfileFeature", dependencies: ["NetworkCore"]),
.target(name: "NetworkCore", dependencies: ["Alamofire"]),
]
)
Package.swiftは2つのライブラリ製品を宣言します:ProfileFeatureとNetworkCore。ProfileFeatureはNetworkCoreに依存しますが、Alamofireの存在は知りません。AlamofireはNetworkCore内に隠されています。このような分離はモジュールレベルでのSoCの直接的な適用です。HTTPクライアントの変更にProfileFeatureの再コンパイルは必要ありません。
モジュール性の主な利点は開発速度です。チームはコードの競合なく異なるモジュールで並行して作業できます。CI/CDパイプラインは変更されたモジュールのみをビルドし、そのテストのみを実行します。フィードバック時間が短縮され、リリース頻度が向上します。Spotify、Uber、Airbnbは、モジュラーアーキテクチャへの移行によりメトリクスが2~3倍改善したケーススタディを公開しています。
2つ目の利点はエラーの分離です。Profileモジュールのバグは、直接的な依存関係がなければPaymentsモジュールに影響しません。これは、支払いや医療データなどの高リスク機能を持つアプリケーションで特に重要であり、無関係な画面でのエラーが重要な機能のリリースをブロックしてはいけません。
主な課題は依存関係管理です。設計が不適切だと、1つのモジュールの変更が数十の他のモジュールのカスケード再ビルドを引き起こすモジュールグラフが発生します。解決策は非循環ルールに従うことです。モジュール依存関係グラフは有向非循環グラフ(DAG)でなければなりません。Gradle Module Graph Assertなどのツールはビルド時に循環を検出するのに役立ちます。
2つ目の課題は初期セットアップ時間の増加です。モジュラーアーキテクチャの作成にはプロジェクト初期化段階により多くの時間が必要です。1~3人の開発者の小規模プロジェクトでは、並列化の真の必要性なくモジュール境界の維持に時間を費やし、モジュール性の恩恵を受けられない可能性があります。解決策はモノリスから始めて、チームの成長に合わせてモジュールを抽出することです。
Feature-firstアプローチは機能ごとにモジュールをグループ化します。各画面または画面グループが個別のモジュールになります。Layer-firstアプローチは技術的な基準でコードを分割します:UI、ビジネスロジック、データ用の個別モジュール。実際には、ほとんどのチームはコアモジュールと組み合わせたfeature-firstを選択します。これにより、より優れた分離と明確なプロジェクトナビゲーションが得られます。
アプローチの選択は、チームのサイズと機能の予測可能性に依存します。プロジェクトにどの画面があるか正確にわかっている場合、feature-firstにより各開発者が自分のモジュールに責任を持つことができます。機能が頻繁に変更され画面間で重複する場合、layer-firstは異なる機能間でのコード再利用においてより柔軟性を提供します。
よくある質問
最適な数はプロジェクトとチームのサイズによって異なります。5人のチームには6~10のモジュールで十分です。20人以上の開発者には20~40のモジュール。ルール:モジュールは1人の開発者が完全に理解できるほど小さく、過剰な依存関係ネットワークを作らないほど十分に大きくあるべきです。
適切なモジュール性は並列コンパイルとキャッシングによりビルドを高速化します。しかし、密な依存関係を持つ過剰なモジュールはビルドを遅くします—GradleとXcodeはグラフの解決に時間を費やします。高速ビルドの鍵は、推移的依存関係を最小限に抑え、非循環性を維持することです。
はい、ただし反復的に行います。コアモジュール(ネットワーク、データベース)の抽出から始め、その後1つずつ機能を抽出します。フィーチャーフラグを使用して、古いモノリシックコードと並行して新しいモジュラーコードを有効にします。大規模なアプリケーションの完全な移行には3~12ヶ月かかります。
モジュールは単一のアプリケーション内のコンパイル単位です。マイクロサービスは異なるサーバーで実行される個別のプロセスです。モジュールはコードを分割し、マイクロサービスはランタイムを分割します。モバイル開発では、「microapps」という用語がハイブリッドとしてよく使用されます。独立したアプリケーションとして実行できるフィーチャーモジュールです。
各モジュールには独立して実行される独自の単体テストがあります。統合テストはモジュール間の相互作用を検証します。UIテストはモックデータを使用してフィーチャーモジュールをカバーします。モジュラーアーキテクチャはテストを簡素化します。別のモジュールの依存関係をモックするのは、モノリスの一部をモックするよりも簡単です。
まとめ
ターンキー方式のモバイルアプリケーションを開発します
IT Sectrは2017年からスタートアップや企業向けにiOS・Androidアプリケーションを開発しています。私たちがご相談に乗り、最適なソリューションをご提案します。