前回の記事の続きのVIPER Clean Architectureの実践編である。
この記事では内側の2つの円の部分まで作成する。今回カバーする部分はとても簡単なのでほとんどの人が問題なく読み進められると思う。
今回作るのは以下の図解の 真ん中の2つの円、EntitiesとUse Cases の部分だ。これを円の内側から順番に作っていく。
The Clean Architecture by Robert C. Martin (Uncle Bob)
なぜ内側から作るのか?
内側から実装するにのには理由がある。
1. 実装しやすい
円の内側は依存のもっとも深部であり、その先に依存する部分がない。依存する先がないとうことは、依存部分のStubを作ったり、その部分がどうなるかを一切想定しなくても作れるということだ。
2. 作る物の本質がわかる
円の内側は本質的に表現したいことであり、抽象的である。UIの表現に惑わされず本質的に何を作ろうとしているかがわかり、理解を促進する。
3. 実践に近い
実際に開発をする場合、最終的に表現されるUIの部分は、最後まで固まらないことが多い。しかし本質的に何を表現するかは、比較的初期に決まり大きく変化しないことが多い。
例えばユーザーの一覧を表示する画面を作成する時、User Entityを作成し、それをAPI経由でlistとして取ってきて、List状に表示するということは初期に確定するだろう。
しかし実際のUIで具体的にどう表現をするかは最後までわからなかったり途中で変わったりする可能性が高い。
例えばUserをListとして表示する場合、
- TableView
- CollectionView
- PageView(各ページに1ユーザーを表示)
のような複数の実装方法が想定可能である。
もしMVC設計して実際のCell等に直接Userを渡して情報を表示させるような設計をした場合、Clean Architectureで分割されている全てのコンポーネントのコードがViewControllerを中心に密接に繋がってしまい、UI変更のコードへのインパクトはClean Architectureで設計した場合と比べ大分大きくなってしまう可能性がある。
作るシステムの概要
Userの一覧を取ってきて表示するという単純なシステムを作成する。
FrameworkとフォルダのGroup分け
VIPERやClean Architectureの恩恵を最大限に受けるためには、それぞれのコンポーネントをしっかりFrameworkとして分けることが重要だ。
これにより、依存関係の理解が促進されたり、間違った実装をしてしまうリスクを大きく減らせる。
さらに、1つの画面の開発をUI部分やロジックの部分と複数人で分業しての開発もやりやすくなる。
Group構造とClean Architectureの円を一致させる
Xcode上でRoot配下のGroupは全てClean Architectureの一つの円を表すようにする。
各Frameworkとその配下のディレクトリ構造は、Xcode上と基本的には一致させる。
しかし一部Clean Architectureの円の構造を表現するためにXcode上でGroupはあるが実態としてのフォルダは作成しない部分がある。
具体的にはFrameworkは全てRoot直下に実際のディレクトリを作成する。各Framework配下のディレクトリ構造も基本的にはXcodeのGroupと一致させる。
ただしClean Architectureの外側2つの円は、円の中に複数のFrameworkがあるため、この部分はXcode上ではGroupを作成するが実際のディレクトリは作成しない。さらにこの配下にあるGroupは実際のディレクトリを持つ。
最後にClean Architectureの図解の外側にもう一つ円を追加する。
最終完成形
この記事ではカバーしていない部分も含め全てが完成した場合、このような構造になる。
- Outer Circle(without folder)
- App Name(framework)
- Application(without folder)
- Routers
- Settings(framework)
- App Name(framework)
- View(framework)
- Interface Adopter(without folder)
- Presenter(framework)
- Gateway(framework)
- Use Case(framework)
- Request Base
- Interactors
- Entity(framework)
- Entities
今回の記事で作る部分
実際今回の記事でカバーするのは以下の部分のみだ。
- Use Case(framework)
- Interactors
- Entity(framework)
- Entities
実装準備
もっとも内側のEntityの円の部分を作成し始める前に、事前準備として、Xcodeのプロジェクト作成直後にできたメインのTargetの部分を一番円の外側に作成するもう一つの円の部分を表現するようなXcodeのグループ構成にする。
今回は UserManager という名称のXcodeプロジェクトを作成する。
作成直後、Groupを以下のような構造にする。
- Outer Circle(without folder: 新たに追加)
- UserManager(最初から存在するものを移動)
- Application(without folder: AppDelegateやInfo.plistなど自動生成ファイルを入れておく)
- UserManager(最初から存在するものを移動)
最初の円: Entity
Frameworkとファイルの配置
最初の円であるEntityのFrameworkを作成し、その配下にEntitiesディレクトリを作成。
そこにUser.swiftのファイルを配置する。
- Outer Circle(without folder: 新たに追加)
- UserManager(最初から存在するものを移動)
- Entity
- Entities
- User.swift
- Entities
User Entity
User Entityは以下のようになる。
public struct User {
public let id: Int
public let name: String
public init(name: String, id: Int) {
self.id = id
self.name = name
}
}
何の変哲もない実装だが、重要なのはUser Entityそのものが他のどのFrameworkにも依存していないとうことだ。
実際にUser structが他のどのFrameworkへもアクセスしていないと同時に、EntityのFrameworkのTargetのFrameworks and Librariesの部分で何もImportしないことでそこを確約する。
2つ目の円: Use Cases
Frameworkとファイルの配置
UseCase Frameworkを追加し後述の図解の通りのGroup構造を作成する。
- Outer Circle(without folder)
- UserManager
- Application(without folder)
- UserManager
- UseCase
- Interactors(New!)
- UserListInteractor.swift(New!)
- Interactors(New!)
- Entity
- Entities
- User.swift
- Entities
さらにUseCaseからはEntityへの参照が必須なのでUseCaseのTargetの部分のFrameworks and LibrariesでEntityをインポートする。
Usecase Interactor
ここで追加したUserListInteractorが今回この層で実装するInteractorだ。
import Entity
public class UserListInteractor {
var users: [User] = []
}
UserListInteractorはEntityをimportしてUser Entityを直接参照しており、UserListInteractorはEntityに依存していることがわかる。
さてUserListInteractorはどうやってUserの一覧を取ってくれば良いのだろうか?ここでGatewayが登場する。Gatewayに関しては次回の記事で扱う。
まとめ
今回は非常に単純な例だったが、Entityが分離されているだけでも実際大きな恩恵を受けられる。小さなプロジェクトの場合はEntityだけを分離してそのほかの部分は一つ外側の円に全て入れるようなミニマムなFramework分けでもいいかもしれない。
次回はいよいよVIPER, Clean Architectureの重要かつ比較的難しい部分のより外側の実装に移っていく。