📚

SPMでモジュール・コンポーネントを表現

2022/01/13に公開

この記事では、Swift Package Manager(SPM)を使い、モジュール・コンポーネントとして表現することでメンテナンス性などを高める手法について説明します。

前の記事をさらに深掘りしました。
https://zenn.dev/usk2000/articles/7c5cf34c3dff5a

モジュール・コンポーネントの定義とSPMでの表現

モジュールとコンポーネントの定義は似ています。モジュールは機能として単体で完結しており単独で使用できるものとし、コンポーネントは単体で完結するが単独では使用しないと定義されています[1]
SPMで使うために、定義を少し変えます。

  1. モジュールは機能の1まとまりで、単独で使用できるもの
  2. コンポーネントはモジュール内に複数あり、細かい機能として完結しているもの

1については、SPMのPackageというクラスに相当し、上位の存在とします。2については、SPMのProductというクラスに相当し、Packageの下位の存在です。SPMの定義として、その下にTargetがあり複数Targetを1つのProductに入れることができますが、依存関係をなるべく複雑にしないために、ProductとTargetは1対1とします。

Package, Product, Targetについてはこちらの公式

サンプルソースコード

https://github.com/usk-sample/MultiTargetSPMSample

複数Productを作る

SPMパッケージの名前として今回はInfraとします。ここにProductTargetとして、Api, Storeを追加していきます。

let package = Package(
    name: "Infra",
    products: [
        .library(name: "Api", targets: ["Api"]),
        .library(name: "Store", targets: ["Store"])
    ],
    dependencies: [
    ],
    targets: [
        .target(name: "Api", dependencies: []),
        .target(name: "Store", dependencies: []),
        .testTarget(name: "InfraTests", dependencies: ["Api", "Store"]),
    ]
)
  • targets.targetを追加します。例えば.target(name: "Api", dependencies: []),
  • products.libraryを追加します。例えば.library(name: "Api", targets: ["Api"]),

このように定義した場合、 Sources配下に.targetnameと同じディレクトリを作成する必要があります。この例ではApiStoreです。
その後、適宜クラスを作成していきます。
作成したサンプルではSourcesの中の構成は

  • Api
    • Api.swift
  • Store
    • DataStore.swift

のようになっています。

Package内のProductを使用する

DomainPackageでInfraPackage内のTargetを使用してみます。

let package = Package(
    name: "Domain",
    products: [
        .library(name: "Domain", targets: ["Domain"]),
    ],
    dependencies: [
        .package(name: "Infra", path: "../Infra")
    ],
    targets: [
        .target(
            name: "Domain",
            dependencies: [
                .product(name: "Api", package: "Infra"),
                .product(name: "Store", package: "Infra")
            ]),
        .testTarget(
            name: "DomainTests",
            dependencies: ["Domain"]),
    ]
)

  • dependencies.packageを追加します。ここでは.package(name: "Infra", path: "../Infra")
  • targetsの中のdependenciesにProductを追加していきます。ここでは.product(name: "Api", package: "Infra"),

このようにすることで、Package内のProductごとに依存関係を追加していくことができます。
将来的にはDomainPackage内でProductを分けることが出てくることも考えられます。その際、InfraPackage内で必要なProductだけ依存に追加することで、依存関係をよりしっかり管理できるようになります。

考察

SPMを使い、前回よりもさらに複雑な依存関係を表現できました。
サンプルでは、主に水平方向に機能を分割していますが、場合によっては垂直方向に分割した方が良いかもしれません[2]。その際、Package, Productの2つで表現していけば、同様に垂直方向の依存関係も実現できます。
また、今回はProductとTargetは1対1としていますが、モジュールとしてのPackageが多数になった場合などは、Packageを統合、ProductとTargetは1対多となるように依存関係を構成していく可能性もあります。

参考

脚注
  1. モジュールとは?ライブラリ、コンポーネントとの違い ↩︎

  2. https://martinfowler.com/bliki/PresentationDomainDataLayering.html ↩︎

Discussion