最近お世話になっているReso CoderさんのFlutter TDD Clean Architecture Courseをやってみます。
GitHubに上がっているコードが問題なく動いたので安心して学習できそうです。
このチュートリアルで作るアプリは、数字に関するトリビアを表示するというものです。具体的にはこのページの一部をアプリで再現します。
全14回あり少し長いですが学習しながら学んだことをアウトプットします。
第1回 プロジェクトの構造
- 今回はコーディングはしません
- 構成の説明となります
TDDとClean Architectureを使う理由
- コードをクリーンに保ちテストすることは重要だから
- プロジェクトが大きくなりにつれビジネスロジックが散在しがちだから
-
ビジネスロジック(Entities)がUIやDBの変更の影響を受けないようにしたいから
-
特にビジネスロジックのUse CasesはDBからデータを取得し処理するため、DataSourceのコードに依存しがちである
- これを依存性逆転の法則により依存方向を変えたい
-
特にビジネスロジックのUse CasesはDBからデータを取得し処理するため、DataSourceのコードに依存しがちである
Clean Architectureの構造
- 図のように階層になっている
- コードを分離する
- 実装でなくインターフェイスに依存する
- 実オブジェクトは依存性注入(DI)する
- 内側方向にのみ依存する
- Entitiesは何にも依存せず、UseCasesはEntitiesのみに依存する
-
しかし赤色のUse CasesがControllersに依存してしまいがちである
- これを依存性逆転の法則により反転させるのがClean Architectureの狙いである
- コードを分離する
このチュートリアルで作るもの
- Numbers APIを利用して、数字の説明を表示するアプリを作る
- 以下のような構成で作る
-
自然に実装するとCall Flowの方向に依存するが、DomainがDataに依存するのは好ましくない
- これを解決するのがCleanArchitectureの大きな目的の1つと言っていい
- 次のようなフォルダ構成を持つ
- /lib
- /core
- /features
- /number_trivia
- /data
- datasources
- models
- repositories(Implementations)
- /domain
- entities
- repositories(Contracts)
- usecases
- /presentation
- /bloc
- /pages
- /widgets
- /data
- /number_trivia
- /lib
Presentation
- Blocパターンを使う
- Widgetsがeventsを送って、Blocからstatesを受け取る
- CleanArchitectureではBlocはPresentationにあたる
Domain
- DomainはCleanArchitectureの内側の層である
- Business logic(use cases)とBusiness objects(entities)を含む
- 他のレイヤーには依存しない
Use Cases
- このアプリのuse caseは以下の2つである
- GetConcreteNumberTrivia: 実際の数字に関する情報を得る
- GetRandomNumberTrivia: ランダムな数字の情報を得る
- 作成するアプリはこの2つを表示するものになる
Repositories
- RepositoriesはDomain層とData層にまたがっている
- Repositoriesビジネスロジックとデータ操作のロジックを分離して、データ操作を隠蔽する
- Repositoryは永続化ストレージが何かについて関知しない
- Repositoryを利用するクラスはビジネスロジックに集中する
- Domain層にRepositoryのインターフェイスが位置する
- Data層にRepositoryの実装が位置する
- Repositoriesビジネスロジックとデータ操作のロジックを分離して、データ操作を隠蔽する
- RepositoryのインターフェイスがDomain層に、実装がData層にまたがる構成よってUseCaseがData層に依存することを回避する
- これが依存性逆転の原則の実現となっている
依存性逆転の原則
- 設計をすすめる上で以下の問題があり、それを解決するための原則
-
問題点: 自然に実装する依存の方向性と設計上合理的な依存の方向性が逆になってしまう
- 例:
- Use Caseがデータを取得するため、Repositoryに依存するコードを書いてしまうが
- Repositoryは変更される可能性が高く、変更した場合に修正点が多くなりすぎる
- 例:
-
解決方法:
- Use CaseがRepositoryの実装ではなくインターフェイスに依存する
-
DI(依存性の注入)等の手法を用いて、Use CaseがRepositoryの実装を読み込む
- DI以外だとFactoryを使う方法がある。
Entity
- NumberTriviaという名のEntityファイルを持ちます
- ここで作成するアプリは単純なものなので、Entityは一つです
- 一般的なアプリは複数のEntitiyを持ちます
- ここで作成するアプリは単純なものなので、Entityは一つです
- Entityはほとんど変化しません
- だから何にも依存せず、他のオブジェクトから依存される設計とします
- 変化するものが変化しないものへ依存するというのが、CleanArchitectureの依存方向の理由になります
- だから何にも依存せず、他のオブジェクトから依存される設計とします
-
APIから取得する情報はEntityではありません
- Data層のDataSourceでAPIにより情報を取得しますが、これはEntityを継承したModelになります。
Data
- Data層はRepositoryの実装とData Sourcesを持ちます
Repositoryの実装
- Repositoryのインターフェイスは前述のようにDomain層に持ちます
Data Sources
- Data Sourcesは以下の2つがあります
- APIを呼ぶRemote Data Sources
- キャッシュしたデータを読み込むLocal Data Sources
- Data SourcesはEntityではなくModelを返します
-
ModelはEntityを拡張した子クラスになります
- ModelはDataSourcesから受け取るJSONを処理します。JSON操作に特化した機能をDomain Entityに持ちたくないので子クラスのModelを使います
- CleanArchitecture図の中央に位置するEntityは他のレイヤーに依存せず、DataSourcesが扱うデータ形式について関知しません
- ModelはDataSourcesから受け取るJSONを処理します。JSON操作に特化した機能をDomain Entityに持ちたくないので子クラスのModelを使います
-
ModelはEntityを拡張した子クラスになります
- RepositoryはRemoteとLocalの2つのDataSourcesを組み合わせます
- RemoteDataSource: Numbers APIに対するHTTP GETリクエストを処理します
- LocalDataSource: shared_preferencesパッケージを用いてキャッシュデータを処理します
- キャッシュはオフラインのときに前回通信で取得したキャッシュデータを返すというような使い方をします
感想と学んだこと
- Flutter TDD Clean Architecture Course[1]の説明をしました。
- Clean Architectureの概要を学びました。
- Layerで分ける
- 外側から内側方向にのみ依存する
-
Use Case(内側)がRepository(外側)を利用する際はRepositoryのContract(Interface)にのみ依存する
- この部分が依存性逆転の原則の適用となる
-
Use Case(内側)がRepository(外側)を利用する際はRepositoryのContract(Interface)にのみ依存する
- Repository層を利用してData Sourcesを隠蔽化する
- RepositoryがDataSourcesから取得するものはEntityを継承したModelとする
- Entityは何にも依存しないため、JSON処理をしないため
- Blocはビジネスロジックという名前だがDomainでなくPresenterとなる
- ただしこの記事ではBlocがビジネスロジックとなっている
- CleanArchitectureの実装方法は複数あるということだと思う
- ただしこの記事ではBlocがビジネスロジックとなっている
- Clean ArchitectureはSOLIDを実現する手法だと感じた
- この記事で指摘されていた
- 次回から実装に入ります