クリーンアーキテクチャーでスマホアプリ開発した感想（勉強会用）

## はじめに

昨年からの大きな案件でClean Architectureを使った

* Platforms: Android/iOS
* Languages: Kotlin/Swift

---

# はじめに

勉強会向け資料なので、クリーンアーキテクチャー自体の解説もある程度含まれます。
逆に、時間の都合上、歴史背景や細かい部分までは行き届いていません。
もし間違いがあればご指摘ください。

---
## オススメ書籍

![51mQrYTahJL.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/277107/23669e0e-88c9-8c68-e17b-f2f4066b8c68.jpeg)

---
## アーキテクチャーを選定する目的

* 求められるシステムを構築・保守するために必要な人材を最小限に抑えるため
  * 「アーキテクチャーは上位レベル、設計は下位レベル」のように区別されることがあるが、両者の間に明確な境界はなく、上位から下位に至るまで、決定の連続である

---
## スマホアプリ開発で代表的なアーキテクチャー

* Androidは**MVVM**（[Googleが推奨](https://proandroiddev.com/mvvm-architecture-viewmodel-and-livedata-part-1-604f50cda1)）
* iOSは**MVC**（[AppleがCocoa applicationに採用](https://developer.apple.com/library/archive/documentation/General/Conceptual/DevPedia-CocoaCore/MVC.html)）

---
# では、Android、iOS両方作る場合はどうする？

---
## ロジックは極力共通化したい

* Android↔︎iOS間で、実装の一部を共通化したほうが、並行開発が楽である
  * 対象はビジネスロジックなど
* XamarinやFlutterのようなX-Platform開発手法もあるが、受託案件では何か起きたら迅速にアップデートできるほうがよいので、以下リスクを考慮して採用しなかった
  * X-Platform：保守できるエンジニアが少ない、Trouble shooting事例が少ない、など

---
## iOSのMVCには課題あり

* ViewにもModelにも属さないコードをControllerに実装しがち
  * 気がつけば膨大なControllerと、小さなView&Modelになっていることが多い

---
# iOSでは代わりにVIPERを使おうという流れがある

---
## VIPERとは
初代GT最速の市販車
![2_3064.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/277107/bdeb113c-81b3-8644-fe88-1ef866168626.jpeg)
のことではない

---
## VIPERとは

* iOSで[MVCに置き換わる](https://cheesecakelabs.com/blog/ios-project-architecture-using-viper/)とされているクリーンアーキテクチャーの一種
* **View**、**Interactor**、**Presenter**、**Entity**、**Router**の略語
* [単一責任の原則](https://qiita.com/gomi_ningen/items/02c42e2487d035f9c3c8)に基づく
* Androidでは、ViewとRouterの分離が難しいので、適用が難しい
  * [VIPE+Rと考える](https://cheesecakelabs.com/blog/using-viper-architecture-android/)案も一応ある

---
# ならもうクリーンアーキテクチャー適用で充分では？（経緯はちょっと違うかも）

---
# その前に、今出てきた「○○の原則」をもうちょっと説明

---
## 重要なSOLID原則

* **SRP: 単一責任の原則**
  * 1つのモジュールはたった1つの役割に対して責務を負う
* **OCP: オープン・クローズドの原則**
  * 拡張に対して開かれており、修正に対して閉じている
  * 要は、変更が発生した場合、既存のコードは修正せず、新しくコードを追加して対応する
  * オブジェクト指向設計の核心で、再利用、保守、柔軟性のメリットが受けられる
* LCP: リスコフの置換原則
* ISP: インターフェイス分離の原則
* **DIP: 依存関係逆転の原則**
  * 抽象モジュールを具象モジュールに依存させるべきではない。具象を抽象に依存させるべき

---
## 依存関係逆転の原則（DIP）

![non-dip.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/277107/ccc0ffeb-d5d5-5e73-6348-641f3311d7ad.png)

* この構成は、抽象（BusinessRule）が具象（SQLDatabase）に依存している:heavy_multiplication_x:
  * ダメな理由は2ページ後で説明

---
## 依存関係逆転の原則（DIP）

![dip.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/277107/6a8ab561-17d4-373d-2051-dbc63c2e6935.png)

* 具象（SQLDatabase）が抽象（BusinessRule）に依存するようになった:heavy_check_mark:
  * このほうが柔軟なシステムとなる
* Clean Architectureにおいて特に重要な原則

---
# なぜ逆転させるのか

* 技術は変わりやすく廃れやすい
* 使っていたOSSが更新されなくなったので乗り換えたい！
  * けど上位レイヤーが依存しまくっている…
* パフォーマンスが悪いので別のDBに乗り換えたい！
  * けど上位レイヤーが依存しまくっている…
* 一方で上位レイヤーのビジネスルールはそう簡単には変わらない
* 変わりにくいものを変わりやすいものに依存させると、システム全体が不安定になる
* 依存関係を逆転させれば、抽象モジュールへの影響を最小限に抑えつつ具象モジュールを変更できる
* 単体テストの作成・実施も容易になる
  * インタフェースに適合するモックを用意すれば、テスト用の具象モジュールは不要

---
# クリーンアーキテクチャーとは（本題）

---
## 一枚図

![CleanArchitecture.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/277107/9f18fbf8-7f01-82ae-4589-af13600d5eb1.jpeg)

https://blog.cleancoder.com/uncle-bob/2012/08/13/the-clean-architecture.html

---
## 図の説明

前頁の図で、

* 矢印は依存関係を示している
* 右下の図は制御の流れを示している
* UIもDBも外側にいる
* 円の外は外界

---
## 各レイヤー（下に行くほど変わりやすい）

![CleanArchitectureCone.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/277107/179add87-8075-baca-4829-e5b6e0b7413b.jpeg)

---
## レガシーな階層アーキテクチャーは大体こんな感じだった

[プレゼンテーション層(UI)]
　　　↓
[ドメイン層(ビジネスルール)]
　　　↓
[永続化層(DB)]

---
## 基本原則

* ソースコードの依存関係は、内側(上位レベルの方針)だけに向かっていなければならない
  * ビジネスルールはフレームワークに依存しない
  * ビジネスルールは単体でテスト可能
  * ビジネスルールはUIに依存しない
  * ビジネスルールはデータベースに依存しない
  * ビジネスルールは外界のインターフェイスに依存しない
* 図は概要なので、上記原則が満たされていれば何層でも構わない
  * ただし過剰な階層化は生産性を下げるので注意

---
## 境界線を引くための指針

* 重要なものと重要でないものの間に線を引く
  * DBはビジネスルールにとって重要でないので、線を引く
  * UIもビジネスルールにとって重要ではないので、線を引く
* 変更の軸があるところに線を引く
  * UIはビジネスルールと異なる理由・頻度で変更されるので、線を引く
  * ウェブとのインターフェイスは以下略
* 単一責任の原則(SRP)が境界を引くための指針となる

---
## コンポーネントのレベル

* 入出力との距離でコンポーネントのレベルを判断する
  * 入出力に近いほど下位
* 下位のコンポーネントを円の外側に、上位を内側に配置する
* 下位を上位に依存させる(依存関係の逆転)

```
上位　　　　Data Conversion
↑　　　　　　　　　Encryption　Translation
│
↓　　　Database　　　View　　　HTTP
下位　　　　　　File
```

---
## 制御の流れ

![名称未設定.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/277107/a90cc4ff-f4ec-b010-0955-49e62801fa58.png)

* すべての制御はUse Casesを介して行われるべき
* 外側のコンポーネント同士が直接データをやり取りすべきではない(環状道路の罠)

---
## Entities

* Enterprise(企業の) Business Rules(ビジネスルール)
* 最重要ビジネスルール・最重要ビジネスデータを指す
  * 銀行の利子計算とか
  * オンラインストアなら商品(を売ること)かな
* 企業(組織)内で共通するルール・データを記述する部分
  * 企業のソフトウェアでなければアプリケーション固有のビジネスルール・データ(後述)となる
* DBとかE-Rモデルでいうところの「エンティティ」とは少し違うので注意
  * この層は特定のデータベースに依存しないため
  * 「ルール」なので何らかのメソッドを持ったクラスかもしれない

---
## Use Cases
* アプリケーション固有のビジネスルール
* そもそもビジネスルールって？
  * ビジネスルールは、ソフトウェアが存在する理由である
  * 「手段」と混同してはいけない
    * データベースやUIなど下位の詳細に関わるべきではない

```
1. テキストボックスに入力された宛先・件名・本文を受け取る
2. 宛先を検証する。宛先が適切なら次に進む
  a. 宛先が空か不正ならダイアログでエラーを通知して終了
3. 件名を検証する。件名が適切なら次に進む
  a. 件名が空なら警告を表示する
    i. ユーザがOKボタンを押したら承認したら次に進む
    ii. ユーザがキャンセルボタンを押したら拒否したら終了
4. メールを送信する
```

---
## Interface Adapters

* ユースケースと外界(UIやデータベース)とのデータ変換などを担う部分
* MVxxアーキテクチャはここに属する
  * View, ViewModel, Presenter, Controllerなど
  * Modelはユースケースの集まりと考えられるので上位
* エンティティとデータベースのフォーマットとの間の変換もここで行う
  * SQLならSQL文、RealmならRealm Object

---
## Frameworks & Drivers
* 外部との境界で、フレームワークやツールを使う部分
* あまりコードを書かない
  * 実際つくるとしたらInterface Adaptersと一体になる気がする

---
## メインコンポーネント

* システムの入り口(main関数)を含む部分
  * AndroidならApplicationやMainActivityを含むモジュール
* 最下層に属する
  * ので、唯一すべてのコンポーネントへの参照が許される
* DIフレームワークとかを使って依存関係の注入を行う

---
## 「詳細」なもの

* データベースは詳細
  * データの保存にファイルを使うのか、RDBMSを使うのかは、アーキテクチャ的に重要ではない
* ウェブは詳細
  * ウェブは集中と分散の歴史をひたすら繰り返している(つまり変わりやすい)
  * ウェブは入出力デバイスのひとつと考える
* フレームワークは詳細
  * フレームワークは便利だが、そこに依存すると簡単に抜け出せなくなる
  * フレームワークと結婚するな。フレームワークとは距離を置け(使うなとは言っていない)

これらが円の内側に入り込まないように気をつける

---
## ビジネスルールは本当に具象に依存すべきでないのか

* すべての具象を排除することは不可能
* Stringは具象だが、Stringなしで開発することはできない
  * Stringは安定しているので使っても問題ないと考える
* Listなどのコレクションも同様に考える
* じゃあRxは？
  * Rxが今後も廃れる心配がないなら使ってよいと思う
  * Android Clean ArchitectureのサンプルはRxJavaを使っているらしい

---
## 優れたアーキテクチャとは

* 開発しやすい
  * 適切に分割されたコンポーネントがあれば分担しやすい
* テストしやすい
  * ユースケースがフレームワークに依存しないので独立したテストができる
* 保守しやすい
  * 「洞窟探検」のコストを抑える
* 選択肢を残しておく
  * データベース、UI、プロトコルなど詳細に関する決定を先送りにできる
  * 「ソフト」とは柔軟に変更できるという意味。変更できることにソフトウェアの価値がある

---
## まとめ

* Clean Architectureとは
  * ユースケースをシステムの中心として捉え
  * ユースケース(上位の方針)とフレームワーク等(下位の詳細)を分離し
  * 依存関係の逆転によって下位を上位に依存(プラグイン化)させ
  * コンポーネントごとのテストを容易にし
  * 詳細部分の置き換えを容易にする設計手法

「速く進む唯一の方法は、うまく進むことである」（Robert C. Martin）

---
## 今回のアプリ設計（コンポーネント図）

![components_75percent.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/277107/45509f14-46e2-6dd2-93a2-6debfb67766d.png)

---
## uiモジュール

* UIを実装。OS依存
  * ViewはActivityやFragmentを指す。ViewModelのデータを表示し、ユーザの入力に応じてViewModelにコマンドを発行する
  * ViewModelはInteractorから受け取ったデータを表示可能な形式に変換したり、コマンドを受け取ってInteractorのメソッドを呼び出す

---
## domainモジュール

* ビジネスロジックを実装する。**OS非依存**
  * **Interactor**はユースケースを表し、ViewModelから見たModelに相当する。ビジネスロジックはここに書く。ユースケースごとにクラスを作成する
  * **Presenter**は処理したデータをViewModelに渡すためのインタフェース。表示に適したデータ加工（ソートなど）は行わない
  * **Repository**はデータへのアクセス手段。データがサーバーにあるかローカルにあるかをInteractorが意識しなくて済むよう隠蔽する。サーバから取得したデータをDBに保存するなどの処理を行う。データのCRUDに専念し、複雑なロジックは書かない
  * **Service**はサーバや端末センサーにアクセスするためのインタフェース
  * **DataStore**はローカルのデータベースにアクセスするためのインタフェース
  * **Model(図では省略)**はエンティティ(ドメインモデル)の集まりで、アプリで扱うデータを単純なデータクラスで表現する

---
## dataモジュール

* データの永続化を行う。OS依存。今回はRealmを使用
* UIに関する設定はここではなくuiコンポーネント内でより簡素な方法で保存する

---
## backendモジュール

* サーバーとの通信を行う。OS依存。今回はRetrofit/OkHttpを使用

---
## sensorモジュール

* 端末センサーからのデータ読み出しを行う。OS依存

---
## utilityモジュール

* ビジネスロジックと直接関係のないユーティリティはここに実装する
  * ログ機能、拡張関数など
* **OS非依存**（domainからも参照可能にするため）
  * OS依存のAPIを使う場合はinterface化して実装を別コンポーネントに持たせる

---
## appモジュール

* メインコンポーネント。各コンポーネントの初期化などを行う
* DIコンテナ（Kodein/Swinject）はここで管理し、各クラスのコンストラクタやプロパティを通じて、依存性の注入を行う

---
# クリーンアーキテクチャーを導入してみて（メンバーへのアンケート結果）

---
## Q1: クリーンアーキテクチャーの理解はスムーズでしたか？

* そう思う(5pts)………………2人
* ややそう思う(4pts)………………0人
* どちらとも思わない(3pts)………………3人
* ややそう思わない(2pts)………………1人
* そう思わない(1pt)………………0人

平均：3.50pts

---
## Q2: クリーンアーキテクチャーを使ったことにより、保守しやすいコードが書けたと思いますか？

* そう思う(5pts)………………2人
* ややそう思う(4pts)………………4人
* どちらとも思わない(3pts)………………0人
* ややそう思わない(2pts)………………0人
* そう思わない(1pt)………………0人

平均：4.33pts

---
## Q3: クリーンアーキテクチャーを使ったことにより、質の高いコードが書けたと思いますか？

* そう思う(5pts)………………4人
* ややそう思う(4pts)………………2人
* どちらとも思わない(3pts)………………0人
* ややそう思わない(2pts)………………0人
* そう思わない(1pt)………………0人

平均：4.67pts

---
## Q4: クリーンアーキテクチャーを使ったことにより、開発時間が短縮できたと思いますか？

* そう思う(5pts)………………0人
* ややそう思う(4pts)………………1人
* どちらとも思わない(3pts)………………3人
* ややそう思わない(2pts)………………1人
* そう思わない(1pt)………………1人

平均：2.67pts

---
## よかった点(1)

* 構造化の度合い
  * 各クラスの責務が小さくなり、内容を理解しやすかった
  * 各クラスの役割がある程度明確になった
  * それぞれのレイヤー毎に単体テストできた
  * 上手くモジュール分けされているので"この部分が未定だけどそこはstubにして他を先に実装する"がしやすい

---
## よかった点(2)

* 一貫性・保守性
  * 指針が明確になったことで、誰が作っても同様の設計になり保守性が上がった
  * レビュー時にも「クリーンアーキテクチャーに基づいているかどうか」という明確な観点からコードを見ることが出来てやり易かった
  * ほぼほぼSOLID原則に従った実装になりやすかった
  * 仕様変更時の改修量が少なかった（例：サーバーAPIが急に変更になったが、backendのJSONObjectクラス1つを修正するだけで済んだ）
  * ほとんどのクラスがコンパクトになった
  * モジュール毎に機能を分けて書いていき、それぞれに必要以上に依存関係を持たせないので保守がしやすい
  * 質の高いコーディング以外受け付けないので、強制的に質が高くなるところが良い

---
## よかった点(3)

* 理解可能性
  * 修正箇所を見つけるのも意外と苦ではなかった
  * どの機能においても似たようなコーディングの仕方になるので、後から"この機能どうなっているんだっけ"と確認する時に探しやすかった

---
## 改善すべき点(1)

* 構造化の度合い
  * uiモジュールの各クラスが肥大化した（ViewとNavigationを両方含んでいるからと思う）
  * PresenterとViewModelを分離すべきだったかも
  * もしくは、ControllerとPresenterの実装を分けるべきだった
  * いちいちUseCase(Interactor)を通さないといけない場合があるので、それが面倒
  * クリーンアーキテクチャの模範例に従いすぎずモジュールに柔軟性も持たせた方がよかったかも
  * ファイル数が多い。
  * 仕様が読みづらくなるので、品質の高い仕様書（UML）を書くべき。

---
## 改善すべき点(2)

* 理解可能性について
  * アーキテクチャーの学習コストがかかった（選定に関わった人は習得が早いが、そうでない人はちょっと苦戦する傾向あり）
  * 敷居が高い。理解するまでに時間がかかった。
  * どこに責務を持たせるか、人によって判断の違いが若干あり、その場合議論が起きやすい（レビューの長さが時々ボトルネックになっていた）

---
# 当日の質疑応答の内容

---
## クリーンアーキテクチャーの苦手なポイントとは

* MVCなど他アーキテクチャーを使っていたら移行に長い時間がかかる
* 一部の開発者はこのパターンを理解するのが困難
* スクラムでやるくらいの規模の開発には向いているが、個人開発だとやりすぎ感あるし、（チーム的にも量的にも）大規模な開発は苦手と感じた
  * Entity部分の肥大化に伴う構成の複雑化、保守性や可読性の低下が問題となってくると強みを活かせない
  * が、各クラスが小さくなるというメリットは大規模になっても活きる

---
## データのコピーや変換による速度低下・オーバーヘッドは問題にならないか

* 異なるモジュールにデータを渡せばコピーや変換は発生するが、繰り返し何度も変換するわけではない
  * 例えばdata→domainならDBに依存する型からdomainで扱える簡素な型への変換が必要だが、domain→UIは変換は必要なく、描画処理で吸収すればよい
  * 今回の開発では、多くてもbackend→domain→data→domainの3度の変換で済み、目に見える速度低下は無かった
* すごく長いリストを扱うと、トータルで何割かのオーバーヘッドが起きて問題になるかもしれない
  * が、そのせいでアーキテクチャーを諦める前に、リストの最大数を制限する仕様としたり、言語のアンチパターンを取らないなど、他の努力でカバーできる
  * また、理由があればjson形式のまま表示処理まで渡してよいなど、柔軟な対応をしたほうがよいと考える

---
## 他に検討したアーキテクチャーは？

* MVC
* MVP
* MVVM
* Flux
* Redux
* MVW
* Angular

---
## 何故クリーンアーキテクチャーを選んだのか

* スマホアプリのクロスプラットフォーム開発に向いているとの情報が散見されたから
* iOSのMVCの課題を解決する見込みがあったから
* 品質に重きを置いてほしいと言われていたから
  * 単体テストしやすいアーキテクチャーとして選んだ
* メンバーの中に得意とする人がいたから
* …あとは直感（大事）
  * エンジニア招集から開発着手まであまり日数が無かったため、1つ1つのアーキテクチャーをじっくり選定している時間は無かった

---
## 参考文献

* Clean Architecture 達人に学ぶソフトウェアの構造と設計　Robert C.Martin(著), 角 征典(訳), 高木 正弘(訳)
* https://blog.cleancoder.com/uncle-bob/2012/08/13/the-clean-architecture.html
* https://proandroiddev.com/mvvm-architecture-viewmodel-and-livedata-part-1-604f50cda1
* https://developer.apple.com/library/archive/documentation/General/Conceptual/DevPedia-CocoaCore/MVC.html
* https://cheesecakelabs.com/blog/ios-project-architecture-using-viper/
* https://cheesecakelabs.com/blog/using-viper-architecture-android/
* https://qiita.com/gomi_ningen/items/02c42e2487d035f9c3c8
* https://medium.com/eureka-engineering/go-open-closed-principle-977f1b5d3db0
* https://docs.google.com/presentation/d/1zamntmxKcupdPDtvQS2SSKxLiw1RxcjwF18litud6os

---
# ご清聴ありがとうございました。