Clean-ish アーキテクチャ: Flutter×Firebaseで0→1を最速に、1→10を伸ばす3.5層設計!
🔥 「Clean-ish」アーキテクチャを提唱したい!
私はクリーンアーキテクチャやDDDが好きでよく個人開発などでも利用しています。
(完全に理解しているかと言われる危うい😓)
ただし、ふと思いました。
「(個人開発や小規模PJにおいて)こんなにクリーンを保つ必要があるのか?コードやアーキ特有のルールを増やして自分の負担でかくなってるだけなんじゃないか?」
そこで、クリーンさ、拡張性は残しつつも初期段階で爆速でアプリをリリースするためのモバイルアプリアーキテクチャ。
「Clean-ishアーキテクチャ(クリーンっぽいアーキテクチャ)」を考えました!
🏁 この記事で扱うもの
| 項目 | 内容 |
|---|---|
| 👤想定読者 | Flutter と Firebase で MVP/個人開発を爆速で作りたい人 |
| 🏢規模感 | 1~3 名、ローンチ初期/PMF 探索中 |
| 🎯ゴール | 最速で仮説検証 → 当たった後もリファクタしやすい プロジェクト構造を掴む |
※PMF = Product Market Fitの略
🔎 なぜFlutter × Firebaseに向いていると思うか
そもそもFlutterとFirebaseを技術選定する場合のメリットは以下だと思います。
- UI爆速(Web,iOS,Android)のクロスプラットフォームである。
- サーバーレス爆速(サーバーの用意など不要)である。
そしてこのメリットが最も刺さるフェーズが以下だと思います。
- ビジネス初期段階(スタートアップや、サービス検証フェーズ)
- 個人開発
- (小中規模アプリ)
この場合、ビジネスの転換などによってドメインのルールが変わる、UI/UX検証によって画面構成が変わる、などさまざまな変更が考えられます。
そのため、変更容易性を担保するために疎結合とするクリーンアーキテクチャが良い、と私は考えていたのですが、、、
なんにせよコード量(ファイル数)の増加や、クリーンアーキテクチャのルールに沿っているかを考える時間ネックでした。
そこで考えたのが
Clean-ishアーキテクチャです。
💎 Clean-ishアーキテクチャとは?
このアーキテクチャの思想は以下です。
クリーンさと初速の“いいとこ取り” を目指す 3.0〜3.5 層構成。
0→1 を 最短リードタイム で倒し、1→10 で 必要箇所だけクリーン度を濃く していく。
概念図
ディレクトリ構成
lib/
├─ data/ # Infrastructureでも可
│ └─ repositories/
│ └─ user_repository_impl.dart # 実装クラス
├─ domain/ # Application + light Domain
│ ├─ models/ # Entity(Freezedで定義)
│ ├─ repositories/ # repositoryのinterface
│ └─ usecases/ # UserUseCase など
└─ presentation/
├─ view/
└─ viewmodel/
| レイヤ | 0→1 で入れるもの | 1→10 で追加するもの |
|---|---|---|
| presentation | View / ViewModel | Widget 拡張・UIテスト |
| domain | Entity (Freezed) / UseCase / Repository interface | Domain Service / パッケージ分割 |
| data | 最小 Repository Firestore impl | LocalDB/別バックエンド impl |
※repositoryの実装クラスは「infrastructure層」に置くプロジェクトも見かけます。
📝 ディレクトリ別 “なぜ作るのか?” メモ
Clean-ish のキモは “理由がある最小構成”。
各フォルダに置く意義を明文化しておくと、あとでチームメンバーが増えても迷いません。
domain/models ― Entity
お馴染みの(?)Freezedを使ったEntityです。
class User with _$User {
const factory User({
required String id,
required String name,
required int point,
}) = _User;
factory User.fromJson(Map<String, dynamic> json) =>
_$UserFromJson(json);
}
❓なぜentityは作成するのか?
- 型安全 & fromJson/toJson 自動生成
- プロジェクト初期(小規模PJなら初期に限った話ではないかも?)はFirestoreのスキーマ=アプリ内でそのまま扱える場合が多いため、dtoなどは作成しない。
- factoryを使えばドメインルールの記載も可能(clean-ishアーキテクチャでは必須ではないと考える)
- 1→10でDDDっぽく倒すならvalue objectやドメインサービスに切り出すことも可能
domain/usecases ― UseCase
class UserUseCase {
UserUseCase(this._repo);
final UserRepository _repo;
Future<User> createUser(String name) async {
final user = User(id: uuid, name: name, point: 0);
final res = await _repo.create(user);
return res;
}
}
❓なぜUseCaseは作成するのか?
- viewModelで直接repositoryを呼ぶとviewModelが肥大化する
- UI変更や画面に左右されずに再利用できる
- 初期フェーズでは特に検証次第でUI/UXが変わる場面が多い
domain/repositories ― interface
abstract class UserRepository {
Future<User> read(String id);
Future<User> create(User user);
}
❓なぜrepository interfaceは作成するのか?
- 今後の別 DB/API への 差し替えコストを下げる (実装クラスを替えるだけ)
- interfaceの作成だけならそこまで手間ではないと考えている
- テストの際にもDIしやすい
data/repositories ― impl
初期は、Firestoreを直書きするこのクラスだけで十分!
本来であればFirebaseFirestore.instanceはDIした方がテストなどは楽ですが、初期フェーズなので無視しています。
テストも最初から行うPJであれば、DIした方がいいと思います。
class UserRepositoryImpl implements UserRepository {
final _col = FirebaseFirestore.instance.collection('users');
Future<User> read(String id) async {
final d = await _col.doc(id).get();
return User.fromJson(d.data()!).copyWith(id: d.id);
}
Future<User> create(User user) => _col.doc(user.id).set(user.toJson());
}
❓なぜrepository実装クラスは作成するのか?
- インフラ依存 (Firestore SDK) を domain から隔離
- 実装ファイル単位で Supabase 版/LocalDB 版 に差し替えやすい
- View 層は UserRepository しか知らない=依存の向きが守れる
🔑 Clean-ish を支える “3本柱”
せっかくの提唱(?)なので、かっこいい名称にしてみたw
| 柱 | 一言で | 実装イメージ |
|---|---|---|
|
1. Velocity First 「まず走る」 |
Entity+UseCase+最小 Repo interface だけ置き、残りは直書き。 | Freezed 即生成/Firestore 直書きで UX 検証を即日回す |
|
2. Intentional Seams 「替えどころを 1 枚残す」 |
差し替え・モックが必要になり得る“未来の急所”にだけ interface を挟む。 |
UserRepository だけ DI で切替、他は YAGNI |
|
3. Progressive Hardening 「育ってから固める」 |
複雑さが顕在化した箇所にだけレイヤ追加。 | ロジック重複→Domain Service昇格/オフライン要求→LocalDataSource追加 |
✅ まとめ
- Entity+UseCase+最小 Repo interface だけで “走りながら守る” 土台ができる。
- 差し替えポイント を 1 枚仕込めば、当たった後の方向転換も怖くない。
- “クリーンっぽい” だけど プロトタイプ→スケール が滑らか、それが Clean-ish。
初速は殺したくない、初期リリースまで時間がない。。。
でも、後から改修が大変になるのは避けたい。。。
そんな折衷案が欲しい時は、ぜひClean-ishアーキテクチャを試してみてください。
ご意見や質問、実際に使ってみた!などコメント欄でお待ちしております!😆
(今後このアーキテクチャに関してはアップデートしていきたいと思います!)
Discussion