iOS Clean Architectureで遊んでた話
初めに
今年に入ったくらいからiOS Clean Architectureについて調べたり,チマチマと実装してみて遊んでたりしてたので,そのまとめです.
iOS Clean Architectureとは
概要
詳しい説明は省きますが,iOS Clean Architectureとは,Clean ArchitectureをiOSに適用したアーキテクチャの名称です.
Clean Architectureとは,ソフトウェア開発において,ビジネスロジックを中心に考え,その他のUIや各種Framework,API Client等はそのビジネスロジックに依存するような構成にし,なおかつビジネスロジック以外のものを複数のレイヤに分けることによって関心と責務の分離を目指したアーキテクチャです.
ドメイン駆動開発のDomain Layer・Data Layerをさらに細かくレイヤ分けたしたような構成になっています.いわゆるMVC系統のアーキテクチャを採用して開発を進めるにあたり,Modelの定義がプログラマによってまちまちで,命名もバラバラになってしまっていたものを,きちんと整理して属人性を廃した命名・構成になるようにと提案されたものって感じでしょうか.
出典: The Clean Architecture – 8th Light
上図を用いて説明します.中心の黄色い領域(Entities)と赤い領域(Use Cases)がドメイン(ビジネスロジック)となっており,アプリケーションの中核を成します.EntitiesがAPI Responseや帳票データ等,業務における一般的なロジック(データ構造)を扱っているのに対し,Use Casesはアプリケーション(ここではiOS)固有のロジックを担っています.
一番外側の青い領域は外界となっており,DBやAPI Server,アプリのUI等を扱っています.
そして,それらの間にある緑色の領域(Controllers)がI/Fとなっており,外界とドメインを結ぶ役割を果たします.
上図の依存関係を示す矢印を見ても分かる通り,外界からI/Fを通してドメインに依存する構成となっており,その逆の依存は一切ありません.内側のレイヤはより外側のレイヤのことは一切知らず,自分自身とより内側のレイヤに関する知識のみを持ちます.
このような構成にすることで,比較的安定しているドメインにのみ依存する構成にすることができ,UI等の変更に強いアプリケーションを開発することが可能となります.
細かい構成
実際に実装するにあたり,いろいろと試行錯誤し最終的に下図のような構成に落ち着きました.
Presentation Layer
このレイヤはユーザの目に実際に触れる部分を含んでいます.
Presenter
- Domain LayerとのI/F
- 各種UIイベント等に対し,どのように処理するかを決定し,ViewControllerへ伝える
- 必要に応じてUseCaseを実行する
- UseCaseから受け取ったデータ(Model)をViewControllerへ渡す
- ViewControllerがどうなっているかは知らない
ViewController
- iOSにおけるUIViewControllerを継承したもの
- UIイベントを自身だけで処理せず,どのように対処するかを逐一Presenterへと問い合わせる
- Presenterからの指示によりViewの状態等を変更する
View/Cell
- iOSにおけるUIViewを継承したもの
- 実際にユーザの目に触れる部分
- ViewControllerがどうなっているかは知らない
Domain Layer
このレイヤはビジネスロジックを直接扱う部分を含んでいます.
UseCase
- 各種ユースケースを記述する
- Presenterからの要求に対応する
- 必要に応じてRepositoryへデータ取得を要求
- Repositoryから受け取ったデータ(Entity)をTranslatorを介してModelに変換した上でPresenterへ渡す
- PresenterやRepositoryがどうなっているかは知らない
Translator
- Data Layerで扱うデータ形式であるEntityを,Presentation Layerで扱うデータ形式であるModelへと(相互)変換する
Model
- Presentation Layerで扱うデータ形式
- Data Layerでは扱われない
Data Layer
このレイヤは各種生データを扱う部分を含んでいます.
Repository
- Domain LayerとのI/F
- UseCaseからの要求に対し,DataStoreへ実際のデータ取得を要求する
- 状況に応じて,DBから生データ取得したりAPI経由で取得したりする
- DataStoreから受け取ったデータ(Entity)をUseCaseへ渡す
- DataStoreがどうなっているかは知らない
DataStore
- データを実際に取得更新する
- DBやAPI Server,Realm等,処理の対象毎に用意する
- Factory Patternを用いてRepositoryがDataStoreの種別を意識しなくてもいいようにする
Entity
- Data Layerで扱うデータ形式
- Presentation Layerでは扱われない
たまに出てくる,「〜〜がどうなっているかは知らない」って部分は,依存性逆転の原則を用いてより内側のものがProtocolを定義し,より外側のものがそのProtocolに準拠することにより実装します.
TL;DR 実装してみた
ダラダラとGitHub Repository Searcher的なものを実装してました.
Common
Builder
VCのBuilderです.DIコンテナの役割も果たしています.
Wireframe
VC間の遷移を一手に担います.
Presentation Layer
Presenter
GitHubRepositoryPresenterInput ProtocolがViewControllerに対して準拠を要求するプロトコルとなります.
ViewController
各種Delegateの中でPresenterを介しているところがポイントです.これ,どこまでPresenterでやってどこまでVCでやればいいのかまだ確実な線引きをできていないんですよね.cellの生成部分とかどうしよう.
Domain Layer
UseCase
GitHubRepositoryUseCasePresentationInput ProtocolがPresenterに対して準拠を要求するプロトコル,GitHubRepositoryUseCaseDataInput ProtocolがRepositoryに対して準拠を要求するプロトコルとなります.
Translator
Model
Data Layer
Repository
GitHubRepositoryRepositoryInput ProtocolがDataStoreに対して準拠を要求するプロトコルとなります.RepositoryRepositoryの命名は我ながらナンセンスだと思います.
DataStore
API Clientについては省略
Entity
ObjectMapperを用いて初期化処理を簡易化しています.
ある程度自分の中の構成が固まってきたらKuri等を用いてファイル生成を半自動化するとよさげです.
終わりに
Qiitaばっかりじゃなくたまにはこちらにも技術系の記事書こうと思って書きました.疲れました.
最近のコメント