要約
埼玉大学の学生向けアプリを、Flutter Workspaceによるモノレポ×Feature-firstアーキテクチャで構築しました。中規模チームでの並行開発と長期的な保守性を両立させた設計の実践例を紹介します。
はじめに
Flutterでアプリを作る際、「どんなアーキテクチャを採用すべきか」「モノレポって実際どうなの?」と悩んだ経験はありませんか?
私たちnekonataチームは、これまでFlutterでいくつかのアプリをリリースしてきました。その開発経験を通じて、プロジェクトの規模が大きくなるにつれて、適切なアーキテクチャ設計の重要性を実感してきました。
そして今回、埼玉大学の学生向けアプリ「埼大アプリ」の開発において、新たな挑戦としてモノレポ構成とFeature-firstアーキテクチャを採用することにしました。
この記事では、その選定理由と実装の詳細を紹介します。モノレポ構成とFeature-firstアーキテクチャを組み合わせることで、どのように保守性と開発効率を両立させたのか、実践的な知見を共有します。
対象読者
- Flutterでの中規模以上のアプリ開発を検討している人
- モノレポ構成に興味がある人
- Feature-firstアーキテクチャの実例を知りたい人
- コード生成を活用した開発に関心がある人
モノレポ構成の採用
埼大アプリでは、Flutter Workspaceを使ったモノレポ構成を採用しています。
構成
clients/
├── admin/ # 管理画面(Web)
├── saitama_univ_app/ # メインアプリ(iOS/Android)
└── core/ # 共通ライブラリ
なぜモノレポを選んだのか
コードの共通化と一貫性
メインアプリと管理画面で共通のロジック(データモデル、リポジトリ、サービス)をcoreパッケージに集約することで、重複を排除し、バグの混入を防げます。
同時進化の実現
複数のクライアントを同じリポジトリで管理することで、APIの変更などに対して全クライアントを同時に更新できます。
開発環境の統一
miseを使ってFlutter SDKやNode.js、Firebase CLIなどのツールバージョンを一元管理し、チーム全体で同じ環境を維持しています。
FlutterのモノレポツールとしてはMelosが従来から広く使われていますが、このプロジェクトではGoogleが公式にサポートするFlutter Workspaceを採用しました。公式機能として今後の安定性とサポートが期待できることが決め手となりました。
なぜ管理画面もFlutterで作ったのか
当初はCMSの利用も検討しましたが、最終的にFlutterで独自の管理画面(Webアプリ)を構築することにしました。
CMSの選択肢が限られる
Flutterアプリと連携しやすく、Firebaseとスムーズに統合できるCMSが見つかりませんでした。既存のCMSを採用すると、連携のための追加実装が必要になります。
型の共有による開発効率の向上
メインアプリと管理画面で同じデータモデル(Freezedクラス)を共有できるため、型の不一致によるバグを防げます。APIの変更時も、両方のクライアントで同時に型エラーとして検出できます。
学習コストの削減
Flutter一本で統一することで、新しいメンバーがCMSの使い方を学ぶ必要がなくなります。チーム全体で同じ技術スタックを共有できることは、開発速度の向上にもつながります。
柔軟なカスタマイズ
独自の管理画面を持つことで、プロジェクト特有の要件に合わせた機能を自由に追加できます。CMSの制約を受けることなく、必要な機能を必要なタイミングで実装できます。
Flutter Workspaceは比較的新しい機能ですが、Googleが公式にサポートしているため、今後の発展も期待できます。
Feature-firstアーキテクチャの実践
Feature-firstとは
従来のLayer-first(data層、domain層、presentation層で分割)とは異なり、機能ごとにディレクトリを分割するアプローチです。
lib/features/
timetable/ # 時間割機能
presentation/ # UI
domain/ # ビジネスロジック・モデル
repository/ # データアクセス
provider/ # Riverpodプロバイダー
service/ # ビジネスサービス
bulletin_board/ # 掲示板機能
presentation/
domain/
...
採用した理由
変更の局所化
新機能の追加や既存機能の修正時に、関連するファイルが1つのディレクトリにまとまっているため、変更範囲が明確になります。
チーム開発での並行作業
機能ごとに分離されているため、複数の開発者が異なる機能を同時に開発する際、コンフリクトが起きにくくなります。
認知負荷の軽減
「この機能のコードはどこにあるか」が直感的にわかるため、新しいメンバーのオンボーディングもスムーズです。
AIコーディングツールとの相性
関連するコードが一箇所にまとまっているため、AIに提供するコンテキストが限定され、より精度の高いコード生成が可能になります。GitHub CopilotやClaude Codeなどのツールを活用する際、機能単位でファイルが整理されていることで、AIが適切な参照コードを見つけやすくなります。
主要パッケージ
状態管理:Riverpod + コード生成
hooks_riverpodとriverpod_annotationを組み合わせて、型安全で保守性の高い状態管理を実現しています。
@riverpod
TimetableService timetableService(Ref ref) {
return TimetableService(ref);
}
コード生成により、ボイラープレートを削減し、ヒューマンエラーを防げます。
データモデリング:Freezed
イミュータブルなデータクラスをfreezedで定義し、json_serializableでJSON変換を自動化しています。
@freezed
class TimetableEntry with _$TimetableEntry {
const factory TimetableEntry({
required Course course,
required List<WeekdayPeriod> periods,
}) = _TimetableEntry;
factory TimetableEntry.fromJson(Map<String, dynamic> json) =>
_$TimetableEntryFromJson(json);
}
ルーティング:AutoRoute
型安全なナビゲーションとディープリンク対応をauto_routeで実現しています。
その他の重要パッケージ
- flutter_gen: アセットへの型安全なアクセス
- logger: 構造化ログ出力
コード生成系パッケージを多用していますが、mise run build_runner:allで全クライアントの生成を一括実行できるため、運用上の負担は少ないです。
まとめ
埼大アプリの開発では、以下の3つの柱でアーキテクチャを構築しました:
- モノレポ構成:コードの共通化と一貫性の担保
- Feature-firstアーキテクチャ:変更の局所化と認知負荷の軽減
- コード生成の活用:ボイラープレートの削減と型安全性の向上
このアーキテクチャにより、中規模チームでの並行開発と、長期的な保守性を両立できています。
もちろん、すべてのプロジェクトに適用できるわけではありませんが、中規模以上のFlutterアプリ開発の参考になれば幸いです。
参考