iOS開発でのマルチモジュール構成のメリデメを考える

# はじめに
最近、iOS開発界隈でマルチモジュール構成というものをよく耳にする様になった気がします。
現在試しに私もマルチモジュール構成での開発をやってみて、メリットデメリットが見えてきたので、今後の技術選定の参考にするために記事を書いてみることにします。

:::note
自分なりにマルチモジュール導入の記事を見つつ、メリットデメリット整理しましたが、誤りなどありましたら是非ご指摘ください！
:::

# マルチモジュール構成とは？
一つのアプリを複数のモジュールに分割して、アプリケーションを実装する構成のことです。

モジュールってなんや！という話になるかと思いますが、`import`で呼び出すものがiOS開発ではモジュールと呼ばれます。

例えば、以下のコードで`import SwiftUI`としていますが、`SwiftUI`というモジュールを呼び出して、`View`の生成を行っているということですね。
```swift
import SwiftUI

struct ContentView: View {
    var body: some View {
        VStack {
            Image(systemName: "globe")
                .imageScale(.large)
                .foregroundStyle(.tint)
            Text("Hello, world!")
        }
        .padding()
    }
}

#Preview {
    ContentView()
}
```

こういったモジュールを作成して、`import`を使って各モジュールを繋げて一つのアプリケーションを実装することをマルチモジュール構成といいます。

どんな粒度でモジュール分割を行うのかがわかると、イメージもつくと思いますので、配置図で表してみました。

以下例ですと、
大きく`View`レイヤーと`Model`レイヤーでモジュールを分割しています。
`View`モジュールでは`Core`モジュールで定義した`protocol`を参照して間接的に`Model`モジュールを参照することで`View`レイヤーと`Model`レイヤー両者は互いに直接的には依存しないようになっています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3359094/d6b572af-7698-87ff-af60-e910c1561fc1.png)

以下例では、
大きくFeatureモジュールとModelモジュールで分割しています。
上記例の違いとしては、`View`レイヤーをFeature単位(機能の粒度)で分けてモジュールを定義するところです。(`Core`の役割は上記と同じです)
理由は後述しますが、上記例よりもこちらの例のようにFeature単位で分割してマルチモジュール構成を実装することがほとんどになる様に思っています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3359094/e795bd2a-e347-220b-b660-056ad2aa1a12.png)

# マルチモジュール構成のメリット
まずずらっと私が感じたマルチモジュール構成のメリットを挙げます。その後、各メリットについて考えている詳細を説明していきます。
- ビルド速度の改善
- 依存関係の可視化
- モジュール間を疎結合に出来ることで、コード修正時の影響範囲を限定できる

### ビルド速度の改善
マルチモジュール構成を導入したいモチベーションとして、割と大きな割合を占めているのは、このメリットを享受するためではないかと考えています。
実際に自分でも試してみましたが、かなりビルド速度は改善されているように感じました。(特にUIの実装をする時にPreviewがサクサク動く様になったので開発の負担がかなり減りました)

なぜビルド速度が改善されるかというと、モジュール同士が疎結合になるためビルドしたモジュールに対してビルドをかけてもアプリ全体をビルドせずに済むためです。

例えば、以下構成を考えたときに、`View`のモジュールをビルドしようとすると、ビルド対象になるのは`View`モジュールと`View`モジュールが依存している`Core`モジュールのみになります。
そのため、単純にビルド対象が限定されてビルド速度が速くなるということです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3359094/d6b572af-7698-87ff-af60-e910c1561fc1.png)

注意点としては、マルチモジュールにしたからといって、モジュール同士を疎結合にしていなければビルド速度は改善されないので、モジュールの依存関係の解決をする仕組みは必須になります。

### 依存関係の可視化
マルチモジュール構成を導入しようとすると、分割したモジュールがどのモジュールに依存するのか検討することになると思います。
そうすると、モジュール間の依存関係は自然と可視化されます。

お馴染みの以下配置図の様に、矢印の向きに依存関係があることが、わかる様になります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3359094/d6b572af-7698-87ff-af60-e910c1561fc1.png)

SPMでマルチモジュール構成を行う例になりますが、`Package.swift`を見ることでモジュール間の依存関係が見えます。

```swift
import PackageDescription

let package = Package(
    name: "SampleLibrary",
    platforms: [.iOS(.v17)],
    products: [
        .library(
            name: "SampleLibraryView",
            targets: [
                "SampleLibraryView",
            ]),
        .library(
            name: "SampleLibraryModel",
            targets: [
                "SampleLibraryModel",
            ])
    ],
    targets: [
        .target(name: "SampleLibraryView",
                dependencies: ["SampleLibraryCore"]),
        .target(name: "SampleLibraryModel",
                dependencies: ["SampleLibraryCore"]),
        .target(name: "SampleLibraryCore"),
        .testTarget(name: "SampleLibraryViewTests"),
        .testTarget(name: "SampleLibraryModelTests")
    ]
)
```

このようにあるモジュールが何に依存しているかは、一つのファイルを見ればすぐわかります。

依存関係が可視化されることで、機能実装などで設計を行うときにモジュール間の依存を意識して、設計できるようになるのが良さかなと考えています。
依存を意識せずに設計ができてしまうと、密結合であったり、依存注入できるような設計にできておらずテストしずらい設計になってしまう事故が起きやすくなると考えます。
そのため、依存関係が可視化できていれば依存の向きだったりを意識できる様になるので、疎結合であったりテストしやすい設計に仕上げやすいように思います。

### モジュール間を疎結合に出来ることで、コード修正時の影響範囲を限定できる
あるFeatureに対して修正を行った時に、マルチモジュール構成でなければ他のFeatureであったりModel層やテストに余計な影響を与えていないかは、実際にコードを見たりUTを実行したりしないとわからないと思います。
そのため、並行して複数のFeatureに手を入れるような開発をすると、コンフリクトをたくさん起こしてしまったりします。
そういう状況ではfeatureブランチを切ってfeature毎に別ブランチで開発する運用にすることで、対応したりしますが、複数のバージョンが並走したりする中でそういったブランチ運用をするとかなり複雑になってしまったりもします。
また、CIでは毎度全てのテストを実行したりする必要があり大規模なプロダクトになると、CIの時間でかなり時間がかかってしまうことになります。
上記諸々の事情で、開発が非効率になるケースがあるのです。

しかし、マルチモジュール構成にしてFeature毎にモジュール分割などをすれば、複数のfeature開発を並行に行ったとしても、基本的には修正範囲をfeatureモジュール毎に閉じられるので、featureブランチを切るといったブランチ運用は不要になり複雑なブランチ運用を避けられます。
またCIでも修正ファイルが一部モジュールだけであれば、そのモジュールに対応したテストを行うだけでよくなるので、CIの負担も減らすことができます。

# マルチモジュール構成のデメリット
私が考えるデメリットとしては、やはり ___ボイラープレートなコードの増加___ かと考えています。
マルチモジュール構成を行うには、依存解決の仕組み導入が必須なのですが、この仕組みを実現しようとすると色々とコードを書かないといけなくなります。
以下記事でマルチモジュール構成を行うための依存解決の仕組みについて、解説している記事がありますが、やはり実際に導入しようとするとソースコードの自動生成欲しくなったりするぐらい、ボイラープレートなコードが必要になったりしています。

https://techlife.cookpad.com/entry/2021/06/16/110000

依存解決の仕組みは、`swift-dependencies`というOSSライブラリを用いれば、多少楽に実装できたりもしますが、OSSを使用するのは憚られるプロジェクトもあるかと思います。

https://github.com/pointfreeco/swift-dependencies

https://tech.uzabase.com/entry/2022/12/27/114409

こういったデメリットがあるため、もしマルチモジュールを導入するなら、ある程度コード量が増えることは覚悟した方が良いですし、
ボイラープレートな実装を避けるために、コード自動生成といった仕組みの検討も必要になるのかなと思います。

# おわり
マルチモジュールのメリットデメリットについて、他の記事を見つつ自分なりに整理してみました。
メリットの恩恵はかなり大きいと感じた一方で、ある程度の規模のプロジェクトでないと旨みはそれほどなく、デメリットによって不必要にコードを複雑になってしまうこともあるので、とりあえずマルチモジュールやってみようと安易に導入するのはやはり危険だなと感じました。

以下で、マルチモジュール構成どういう時に導入するのかみたいなところが、語られていたのでおすすめです。

https://www.youtube.com/watch?v=5p6h5yiQ2PQ&list=PL5y9uEm8_ypUXdixnAhwGW4E93zh0qJcN&index=4
https://www.youtube.com/watch?v=glpfnnDDaz8&list=PL5y9uEm8_ypUXdixnAhwGW4E93zh0qJcN&index=5