はじめに
「1人でアプリを開発する時、アーキテクチャはどこまで作り込むべきか?」
個人開発を始めた当初、私は「保守性の高い綺麗なコードを書こう」と意気込み、厳密な Clean Architecture を忠実に再現しようとしました。しかし、結果は大失敗。1つの小さな機能を追加するたびに Domain、Data、Presentation の各レイヤーでファイルを何枚も作成・往復することになり、完全に開発スピードが停滞してしまったのです。
「個人開発の本質は、ユーザーに価値を早く届け、運用しながら改善し続けること」。
そう痛感した私は、過剰な設計(Over-engineering)を削ぎ落とし、1人でストレスなく開発・運用できる「最小限の構成」へリアーキテクチャしました。
本記事では、現在個人アプリを開発・運用する中で行き着いた、**「開発スピードと保守性を両立させるためのリアルな技術選定と設計」**を共有します。
技術選定の軸:なぜ「引き算」をしたのか
1人でUI、バックエンド、インフラ、さらにはプロダクトの運用まで回す必要があったため、技術選定では以下の3つを徹底しました。
-
ボイラープレート(定型コード)を極限まで減らす
- 綺麗な設計よりも、コード量が少なく直感的に書けることを最優先。
-
iOS / Android の差分吸収コストを最小化する
- OSごとのレイアウト調整に時間を取られないフレームワークを選ぶ。
-
「車輪の再発明」をしない
- 認証やデータベース運用など、BaaSで解決できる部分は自作しない。
採用した技術スタックと「不採用」にした理由
| 領域 | 採用技術 | 検討・不採用にした技術 | 採用・方向転換の理由 |
|---|---|---|---|
| フロント | Flutter | React Native | UI再現性が高く、OS間の表示崩れの修正コストが圧倒的に低いため |
| バックエンド | Firebase | 自作API (Go/Node) | インフラ構築・保守コストをゼロにし、アプリ本体の開発に集中するため |
| 状態管理 | Riverpod | Bloc | Blocはコード量が多すぎて1人開発では疲弊したため、よりシンプルなRiverpodへ移行 |
| ローカル保持 | Hive | SQLite | 複雑なリレーションが不要なため、キーバリュー型で高速かつ扱いやすいHiveを選択 |
アーキテクチャ:3レイヤーの「Feature-First」構成
過剰な階層化をやめ、機能単位(Feature-First)でフォルダを分けるシンプルな3レイヤー構成に落ち着きました。
lib/
├── src/
│ ├── features/ # 機能ごとのモジュール
│ │ ├── auth/
│ │ └── item/
│ │ ├── domain/ # モデルクラス (Freezed)
│ │ ├── data/ # Firestoreとの通信・リポジトリ
│ │ └── presentation/# UI (Screens, Widgets, Notifier)
│ └── common/ # 共通UI・ユーティリティ
└── main.dart
// リポジトリのProvider定義
final itemRepositoryProvider = Provider<ItemRepository>((ref) {
return FirestoreItemRepository(FirebaseFirestore.instance);
});
// AsyncNotifierを用いた状態管理
final itemListProvider = AsyncNotifierProvider<ItemListNotifier, List<Item>>(() {
return ItemListNotifier();
});
class ItemListNotifier extends AsyncNotifier<List<Item>> {
@override
Future<List<Item>> build() async {
final repository = ref.watch(itemRepositoryProvider);
return repository.fetchItems();
}
Future<void> addItem(Item newItem) async {
state = const AsyncValue.loading();
state = await AsyncValue.guard(() async {
final repository = ref.read(itemRepositoryProvider);
await repository.addItem(newItem);
return repository.fetchItems();
});
}
}
開発中に泥臭く解決したハマりポイント(Tips)
- キーボード表示時の Bottom Overflowed エラー
画面下部に固定アクションボタンを置いた際、キーボードを立ち上げると定番のレイアウト崩れが発生しました。
対応:
resizeToAvoidBottomInset: true の指定に加え、SingleChildScrollView + LayoutBuilder でキーボード表示時の高さを動的に計算させるガード処理を入れて回避しました。
- FirestoreのRead回数(コスト)跳ね上がり事件
開発初期、画面遷移のたびに Firestore からデータを直接取得していたため、Console 上の Read 数が勢いよく増えてヒヤッとしました。
対応:
アプリ起動時にローカル(Hive)のキャッシュを参照し、Firestore からは「最終更新日時以降の差分データのみ」を取得するビルドロジックに修正。通信量とFirebaseコストの双方を大幅に抑えられました。
おわりに
個人開発を続けていて痛感するのは、「ベストなアーキテクチャは、プロダクトのフェーズやチーム人数(1人か複数人か)によって常に変わる」ということです。
最初から100点満点の綺麗なコードを目指して開発が止まってしまうくらいなら、まずは60点でも動くものを手早く作り、運用しながらリファクタリングしていく方が、結果としてプロダクトもコードも健やかに育つと感じています。
今後もアプリの改善やパフォーマンスチューニングの過程で得た知見を定期的に発信していきます。
同じく個人開発に取り組まれている方や、同様の構成で開発されている方の参考になれば幸いです。「自分はこんな構成でやっている」といったアイデアがあれば、ぜひコメント欄で教えてください!