何番煎じだよって感じですが、アーキテクチャに対する考え方は割と正解がなくて、自分の中に一つ落とし込んでおいて損はないと感じたため、備忘録という形で記事にさせていただきます。
アーキテクチャとは?
一言でいうと、
アプリケーションを綺麗に実装するための設計方法!
アーキテクチャを考慮しない設計でコードを書いていると以下のような課題にぶち当たります。
- 一つのクラスの肥大化(iOSで言うところのFatViewController)
- ロジックが煩雑になる
- 同じ処理を使い回せない
- チーム開発で役割分担しにくい
- テストがしにくい
- 属人化が進み、引き継ぎが難しくなる
- 機能の追加,修正が困難
- etc…
正直まだまだあるとは思いますが、とにかく設計はこだわってないと後で地獄を見るということさえ伝わればOKです。
Sample App
アーキテクチャを語る上で叩き台にするアプリがいるなーと思ったので作りました。
閲覧するときは、見たいアーキテクチャのブランチ(master/mvc/mvp/mvvm/clean-architecture/clearn-architecture+mvvm)に切り変えてください。
https://github.com/rockname/ArchitectureSampleWithFirebase
FirebaseでユーザーのSignUp/Login,投稿のCRUD処理をする、ものすごくシンプルなアプリです。
FatViewController
まずはアーキテクチャのない世界をみなさんにはお見せしましょう。(ブランチはmasterです)
投稿一覧シーンであるListViewControllerを見てもらうとわかりますが、こんなに単純なアプリにもかかわらずすでにコードが123行あります。
ViewControllerがすべての役割を担わされていることがわかりますね。
非常にFatな状態です。
役割
ViewController
- UIのレイアウト
- UIの更新
- ユーザーのアクション通知
- データの処理
- データの更新通知
- ビジネスロジック
評価
いいところ🙆
- 思考停止でコーディングできる(これはいいところなのか…?)
- UIKitの恩恵を最大に享受できる
悪いところ🙅
- それ以外すべて
Model View Controller (MVC)
Webアプリケーションフレームワークでよく聞くやつですね。
Appleの公式ドキュメントでもiOSアプリ開発はMVCが一般的みたいな書き方がされています。
https://developer.apple.com/jp/documentation/CocoaEncyclopedia.pdf
しかし、iOSのUIフレームワークであるUIKitはViewとControllerが密接に繋がっており(ViewControllerというクラスがあるくらい)、iOSのMVCはよくMassiveViewControllerだなんて皮肉を言われています。。
サンプルアプリでは、ViewとControllerをひとまとめにしたViewControllerとModelの2層で設計しています。
図のように、Modelからの更新通知はDelegateパターンで受け取るようにしています。
役割
Model
- データの処理
- データの更新をViewControllerへ通知
- ビジネスロジック
- ViewControllerの状態の保持
ViewController
- UIのレイアウト
- Modelから通知を受け取りUIを更新
- ユーザーのアクション通知
- Modelの操作
評価
いいところ️🙆
- シンプルな構成でわかりやすい
- Modelのテストが可能に
悪いところ🙅
- ViewとControllerが互いに密接に依存
- 各Modelで同じ処理を書くところが出てくる
- まだまだViewControllerはFat
Model View Presenter (MVP)
https://en.wikipedia.org/wiki/Model–view–presenter
ViewとModelの間にPresenterが挟まる感じのやつですね。
ControllerとPresenterの違いの解釈は人それぞれですが、自分としては
- Controller: Userからイベントを受け取る
- Presenter: Viewからイベントを受け取る
というように考えてます。
サンプルアプリでは、Model,View,Presenterの3層で設計しています。
役割
Model
- データの処理
- データの更新をPresenterへ通知
View
- UIのレイアウト
- ユーザーのアクションをPresenerへ通知
- UIの更新
Presenter
- Modelの操作
- Modelから通知を受け取りViewを更新
- Viewの状態を保持
- ビジネスロジック
評価
いいところ🙆
- まだまだシンプルでわかりやすい
- ユーザーアクションの受け取りをUIKitから分離できたためViewControllerが軽量化
- 機能の追加,更新がある程度やりやすい
- ModelをFirebaseとの通信のみにしたためより抽象化できた
悪いところ🙅
- ViewとPresenterの間で互いに依存関係が生まれてしまっている
- Firebaseの処理が全体に蔓延しているためFirebaseに強く依存してしまっている
Model View ViewModel (MVVM)
https://ja.wikipedia.org/wiki/Model_View_ViewModel
ViewModelの役割としてはPresenterと一緒なのですが、ViewとModelの仲介の仕方が違います。
- Presenter: ViewのUIとModelのDataの更新をその都度受けわたす
- ViewModel: ViewのUIとModelのDataをDataBindingでつなげる
サンプルアプリの構成は以下の通りです。
図のように、RxSwiftを使用してDataBindingは実装しています。
また画面遷移もViewModel内で発火させたかったため、
Navigatorという画面遷移を扱うクラスを用意してそれをViewModelに持たせています。
役割
Model
- データの処理
- データをObservableで包んで返す
View
- UIのレイアウト
- ユーザーのアクションをViewModelへInput
- ViewModelのOutputをUIへバインディング
ViewModel
- ModelとViewのバインディング
- Viewの状態を保持
- ビジネスロジック
評価
いいところ🙆
- まだまだシンプルでわかりやすい
- ユーザーアクションの受け取りをUIKitから分離できたためViewControllerが軽量化
- 機能の追加,更新がある程度やりやすい
- ModelをFirebaseとの通信のみにしたためより抽象化できた
- ViewとViewModelの間での依存関係を一方向にできた
悪いところ🙅
- Firebaseの処理が全体に蔓延しているためFirebaseに強く依存してしまっている
- Rxの学習コストが高い
Clean Architecture
https://8thlight.com/blog/uncle-bob/2012/08/13/the-clean-architecture.html
さて、いきなりややこしい図が出てきましたね。
この同心円状になっている4つの円は内側に一方向の依存関係を持つというルールを表しています。
MVPと比べたときに、各層の対応関係は以下のようになります。
- Modelのデータ処理部分 -> DB
- Modelで扱うデータ型 -> Entities
- View -> UI
- Presenterで扱うビジネスロジック -> Use Cases
- Presenterで受け取るModelからの通知/Viewの状態保持 -> Presenters
- Presenterで受け取るViewからの通知 -> Controllers
ただPresentersとControllersに分けるのは冗長すぎるため、
今回はViewControllerにその役割は委譲しています。
サンプルアプリの構成は以下の通りです。
Clean Architectureは依存関係に重きを置く設計であるため、各層はRxSwiftで一方向につなげています。
PostEntityを中心に置き、それをすべての層で使用します。
間違ってもFirebaseのフレームワークで扱うオブジェクトをUI層で使ったりしてはいけません。
また、この場合UseCaseがRepositoryを持っているわけですが、あくまでも依存関係は
UseCase<-Repository
であるため、UseCaseは扱うRepositoryがどんな実装であるかを知っていてはいけません。
これを実現するために、Repositoryの定義はprotocolで切って実装を見えないようにしています。
これにより、例えばRepositoryをFirebaseで実装していたけどサーバーを新しく立ててRESTApiを叩くようにしたくなったら、Repositoryの実装だけを変えることでそれは実現できます。
DBやUIの変更は割と頻繁に起きることですが、Clean Architectureではそれらを円の最も外側に配置しているため、それより内側の実装は変えることなく最小限の修正で対応できる、というわけです!
役割
Entity
- データの型
UseCase
- ビジネスロジック
Repository
- データの処理
- データをObservableで包んで返す
View
- UIのレイアウト
- ユーザーアクションをUseCaseへ通知
- UseCaseから流れてくるDataとViewのバインディング
- Viewの状態を保持
評価
いいところ🙆
- テストをかなりしやすい
- 機能の追加,更新がかなりやりやすい
- 依存関係が一方向であるため設計の手順がわかりやすい(円の内側から実装すればいい!)
- チーム開発での役割分担がかなり捗る
悪いところ🙅
- 概念がわかりにくいためチーム全体で設計の指針をあらかじめ統一しておく必要がある
- Rxの学習コストが高い
Clean Architecture + MVVM
私は先のClean ArchitectureにMVVMを加えた形のアーキテクチャを採用しています。
変更点はViewの役割の中にある
- UseCaseから流れてくるDataとViewのバインディング
- Viewの状態を保持
をViewModelに切り離した感じです。
アプリケーションのロジックはUseCaseにもたせて、UIのロジックはViewModelに持たせるという考え方ができてかなりしっくりきてます。
まとめ
Fluxも書きたかったけど残念ながら時間切れ。。
個人的には、Clean Architecture + MVVMがオススメですー。
サンプルアプリがシンプルすぎて、これUseCaseいらないんじゃない?となりそうですが、
ここに複数のRepositoryにまたがる処理が入ってきたりするとその恩恵をありありと享受できます。
また、ここおかしいんじゃないの?ってところはドシドシ教えてもらえると嬉しいです。
ありがとうございました。