バックエンドエンジニアたちが複数のFlutterアプリを並行開発していく中で見つけたベストプラクティス
こんにちは。Woven Payment Solution開発グループの大杉です。
私たちのチームでは、 Woven by Toyota において Toyota Woven City で使われる決済システムの開発を行っていまして、普段はKotlin/Ktorによるバックエンドの開発とFlutterによるフロントエンドの開発をしています。
以前の記事でフロントエンドの技術選定をしてからWebアプリの開発を進めていましたが、今ではWebだけに留まらずモバイルアプリも含めて7つのFlutterアプリを運用するまでに開発規模が大きくなってきました。
今回の記事では、バックエンドエンジニアしかいなかった私たちのチームで、効率的に複数のFlutterアプリをバックエンド開発と並行して開発していくために工夫してきたことを紹介したいと思います。
Flutter and the related logo are trademarks of Google LLC. We are not endorsed by or affiliated with Google LLC.
要約
私たちは、決済システムのバックエンド開発とフロントエンド開発をしているチームです。開発しているアプリは、Webは管理画面、モバイルはWoven CityでのPoCに参加される方の利用を想定した決済関連のアプリを提供しています。
決済システムのバックエンド開発と並行して、Flutterアプリを開発していくためには効率的に開発を進めていくことが必要であり、以下のことを実施してきました。
- アプリ共通のアーキテクチャを設計
- がんばらないUIコンポーネントの設計方針を策定
- 技術スタックの統一と開発の流れを定義
アプリ共通のアーキテクチャを設計
バックエンドに限らずフロントエンドのアーキテクチャも時代と共に色々な形のものが提唱されていますが、開発チームやプロダクトのフェーズや性質にあったものを選び、改善していくことが良いことだと思います。
私たちは、バックエンド開発ではクリーンアーキテクチャを採用していることもあり、それに類するLayer-firstなディレクトリ構造でMVVM、リポジトリパターンのみを採用したアーキテクチャをFlutterアプリに適用しています。具体的には以下のようなディレクトリ構成となっています。
ディレクトリ構成
lib/
├── presentations
│ ├── pages
│ │ └── home_page
│ │ ├── home_page.dart
│ │ ├── home_page_vm.dart
│ │ ├── home_page_state.dart
│ │ └── components
│ ├── components
│ ├── style.dart // 共通のスタイル定義
│ └── app.dart
├── domains
│ ├── entities
│ └── repositories // リポジトリのinterface
├── infrastructures
│ └── repositories
└── main.dart
ディレクトリの役割
大きく3つのディレクトリでレイヤー構造を表しています。それぞれのディレクトリの役割について簡単に説明すると、以下のようになっています。
Directory | Layer | Role |
---|---|---|
presentations | プレゼンテーション層 | View, ViewModel, 必要であれば状態を定義 |
domains | ドメイン層 | ドメインモデルやロジック、リポジトリへのインターフェースを定義 |
infrastructures | インフラ層 | APIコールなど、リポジトリ層の実装を定義 |
レイヤーパターン中心の設計ではユースケース層が欲しくなるかと思いますが、現時点ではフロントエンドにほとんどビジネスロジックが存在しないため、ViewModelに集約しています。
私たちが開発しているアプリは、まだ複雑な機能がなく、基本的にページ = 1つのドメインが成り立っているため、この設計で今のところ上手く開発が回っています。PoCなどで新しくアプリを作成する際は、基本的にこのテンプレートで作り始めるようにし、アプリごとにアーキテクチャの差異が生まれないようにしています。
がんばらないUIコンポーネントの設計方針を策定
UIコンポーネントを設計する際、Atomic Designは採用しないことと積極的に共通コンポーネント化しないことにしました。
少しネガティブですが、以下のような理由があります。
- Atomic Designの分類のレベル感をメンバー全員で揃えるのは難しい
- 共通コンポーネントを作り込むことよりもページの実装にフォーカスしたい
- 特に、Flutterで抽象的なWidgetを作り込むのはかなり気合いが必要だった
共通コンポーネントを作る方が正統な進め方だとは思いますが、柔軟に仕様変更しながらアプリを作り変えていくフェーズである現時点ではあまり共通化しない方が短期的なメリットが大きいと判断しました。
技術スタックの統一と開発の流れを定義
状態管理や画面遷移のフレームワークについても色々な技術が生まれては消えていったと思います。初学者はその情報の多さにどのライブラリを使えばいいのか混乱してしまうというのは自分も経験したのでよくわかります。そこで、私たちは以下の技術スタックをどのアプリでも共通して使用することにしています。
技術スタック
Target | Library |
---|---|
状態管理とProviderの作成 | riverpod |
モデルの定義 | freezed |
画面遷移 | go_router |
APIクライアント | dio, openapi-genrator |
プロジェクト管理 | melos |
この状態管理とProviderの作成にはRiverpodを利用しています。RiverpodのProviderという概念はバックエンド開発ではあまり馴染みがなく、また、ハンドコードで実装すると思い思いのProviderを実装できてしまうため、実装フローや適用場所をやや厳密に定義しています。
- 必ず riverpod_generatorを使用してProviderを生成する
- インフラ層のリポジトリとドメイン層のインターフェースをバインドする際は、Providerを使用する
Future<HogeRepository> hogeRepository(HogeRepositoryRef ref) {
final apiClient = await ref.watch(openApiClientProvider);
return HogeRepositoryImpl(
apiClient: apiClient.getHogeApi(),
);
}
- ViewModelはAsyncNotifierProviderで実装し、Viewで必要となるリポジトリのProviderはViewModelに集約する
class HogePageViewModel extends _$HogePageViewModel {
Future<List<Hoge>> build() {
return _fetchData();
}
Future<List<Hoge>> _fetchData() {
repository = ref.watch(hogeRepositoryProvider);
return repository.getList();
}
Future<void> registerData(Hoge hoge) {
repository = ref.watch(hogeRepositoryProvider);
return repository.register(hoge);
}
}
- Viewは、ViewModelからのAsyncValueを監視してUI表示を行う。または、ViewModelを介してリポジトリへCRUD処理を行う
以上のように、リポジトリの実装〜UIとバックエンドの組み込みのフローを規定しており、スプリントのタスクを作成する際もフローに準じた粒度でチケットを分割するようにしています。
おわりに
プロジェクトの中でクライアントアプリの開発優先度が上がったことでフロントエンドの開発ポリシーを規定し、チームで開発が円滑に進められるように工夫して来ました。
Web管理画面の多くが、リスト画面 / 詳細画面 / 編集画面 が基本セットになっていることが多いので、今後はコードジェネレーターを活用してさらに効率よくUI実装ができるような仕込みを取り入れていくことも考えています。
関連記事 | Related Posts
We are hiring!
【Toyota Woven City決済プラットフォームフロントエンドエンジニア(Web/Mobile)】/Toyota Woven City Payment Solution開発G/東京
Toyota Woven City Payment Solution開発グループについて我々のグループはトヨタグループが取り組むToyota Woven Cityプロジェクトの一部として、街の中で利用される決済システムの構築を行います。Toyota Woven Cityは未来の生活を実験するためのテストコースとしての街です。
【Woven City決済プラットフォーム構築 PoC担当バックエンドエンジニア(シニアクラス)】/Toyota Woven City Payment Solution開発G/東京
Toyota Woven City Payment Solution開発グループについて私たちのグループはトヨタグループが取り組むWoven Cityプロジェクトの一部として、街の中で利用される決済システムの構築を行います。Woven Cityは未来の生活を実験するためのテストコースとしての街です。