はじめに
個人開発をしていて、なかなか納得のいくフォルダ構成にできなかったり、
とりあえず開発を進めるうちに、コードがとっ散らかって修正や変更が困難になり、
熱が冷め、開発しなくなったプロジェクトが多々あります。
個人的にFlutterが好きで、スマホアプリの開発もどんどんしたいですが、
上記がネックとなって、少し億劫になっていました。
なので、今回書籍やほかのFlutterプロジェクト、Flutterのアーキテクチャやフォルダ構成についての記事を読み漁り、
現状、自分の中でのベストプラクティスを考えてみました。
前提
この記事ではアプリ側にロジックを持つ前提でアーキテクチャを考えています。
なので、アプリ+バックエンドの構成かつバックエンド側にロジックを持つという場合には適してないと思います。
対象読者
・Flutterで開発していてコードがとっ散らかった経験のある人
・Flutterで開発しているがアーキテクチャやフォルダ構成に自信のない人
目次
アーキテクチャ
アーキテクチャ、元となる設計思想はDDDを参考にしている。
厳密なDDDにするつもりはありませんが、アプリケーションが解決すべき課題をコードで表現する上で、
DDDの思想が良いなと思い、取り入れました。
レイヤー構成
DDDを参考にしているので、構成は以下です
- ドメイン層 - 業務ロジックに関するレイヤー
- アプリケーション層 - アプリのユースケースに関するレイヤー
- データ(インフラ)層 - 通信、DBなどのデータ永続化に関するレイヤー
- プレゼンテーション層 - UIに関するレイヤー
フォルダ構成
フォルダの構成のアプローチには二つの方法があります。
一つはレイヤー優先、もう一つは機能優先です。
レイヤー優先
レイヤー単位でフォルダ分けします。
lib ─ src ┬ domain ─ ドメイン層のコードを配置
├ application ─ アプリケーション層のコードを配置
├ data ─ データ層のコードを配置
└ presentation ─ プレゼンテーション層のコードを配置
小規模で機能が一つしかないようなアプリならこのアプローチで十分です。
しかし、機能が複数あるようなアプリの場合、複数の機能が各レイヤーのフォルダに配置されることになり、
まとまりに欠けます。
なので、私は次に説明する機能優先のアプローチが良いなと思います。
機能優先
機能単位でフォルダ分けし、そのフォルダごとにレイヤーフォルダを作る。
lib ─ src ┬ feature1 ┬ domain ─ 機能1のドメイン層のコードを配置
│ ├ application ─ 機能1のアプリケーション層のコードを配置
│ ├ data ─ 機能1のデータ層のコードを配置
│ └ presentation ─ 機能1のプレゼンテーション層のコードを配置
└ feature2 ┬ domain ─ 機能2のドメイン層のコードを配置
├ application ─ 機能2アプリケーション層のコードを配置
├ data ─ 機能2データ層のコードを配置
└ presentation ─ 機能2プレゼンテーション層のコードを配置
こうすることで、機能ごとのコードのまとまりが良くなります。
もっと詳細に二つのアプローチのメリット・デメリットを知りたい方は以下の記事を確認ください。
Flutterプロジェクト構成のアプローチについて
サンプル
サンプルとして、ネットショッピングのアプリを作ろうとした際のプロジェクト構成について構成図を書いてみたいと思います。
機能としては購入(shopping)と出品(sale)の二つができるとします。
また、簡略化のためユースケースは各機能につき一つとします。
- 購入(shopping) : 購入する
- 出品(sale) : 出品する
lib ─ src ┬ shopping ┬ domain ┬ entity ─ Entityオブジェクトを配置
│ │ ├ repository ─ データ層で実装するインターフェースを配置
│ │ └ value ─ 値オブジェクトを配置
│ │
│ ├ application ─ shopping_item ┬ shopping_item_service.dart ─ 商品購入を実現するユースケースクラス
│ │ ├ input_data.dart ─ 上記のユースケース処理の入力引数
│ │ └ output_data.dart ─ 上記のユースケース処理の出力引数
│ │
│ ├ data ┬ repository ─ ドメイン層のリポジトリのインターフェースの実装を配置
│ │ └ database ─ データベースに関連するコードを配置
│ │
│ └ presentation ┬ components ─ 画面のパーツに相当するWidgetを配置
│ ├ controllers ─ UIの状態に相当するコード(Riverpodを使用したファイルなど)を配置
│ └ screens ─ 一画面に相当するWidgetを配置
│
├ sale ─ shoppingとユースケース以外同じため割愛
└ main.dart
備考
上記のサンプルでは、ユースケースがシンプルで、データの永続化や取得もデータベースしか使っていないため、repositoryフォルダのみですが、サーバーと通信したり、Firebaseを使ったりした場合、もっと複雑になりフォルダ構成も検討する必要があります。
しかし、基本的なレイヤーのルールを守れば、コードがとっ散らかって修正や変更が困難になるという事態は避けられると思います。
また実際のアプリケーション開発では、src
フォルダの直下には、機能ごとのフォルダだけでなく、複数の機能に関するフォルダやコードが配置される想定です。例えば、汎用的な処理だったり、UIで使用する共通的なwidget(例えば、エラーダイアログとかログイン画面とか)のコードです。
ただ、繰り返しになりますが、機能ごととレイヤーごとにフォルダ分けし、コードを分割するルールを徹底すれば、低凝集・蜜結合に陥らずに済むのではないかと思います。
いったん、言葉だけの説明になりますが、サンプル的なプロジェクトも作りたいと思います。