KINTO Tech Blog
Development

バックエンドエンジニアたちが複数のFlutterアプリを並行開発していく中で見つけたベストプラクティス

Takuya Ohsugi
Takuya Ohsugi
Cover Image for バックエンドエンジニアたちが複数の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を実装できてしまうため、実装フローや適用場所をやや厳密に定義しています。

  1. 必ず riverpod_generatorを使用してProviderを生成する
  2. インフラ層のリポジトリとドメイン層のインターフェースをバインドする際は、Providerを使用する

Future<HogeRepository> hogeRepository(HogeRepositoryRef ref) {
  final apiClient = await ref.watch(openApiClientProvider);
  return HogeRepositoryImpl(
    apiClient: apiClient.getHogeApi(),
  );
}
  1. 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);
  }
}
  1. Viewは、ViewModelからのAsyncValueを監視してUI表示を行う。または、ViewModelを介してリポジトリへCRUD処理を行う

以上のように、リポジトリの実装〜UIとバックエンドの組み込みのフローを規定しており、スプリントのタスクを作成する際もフローに準じた粒度でチケットを分割するようにしています。

おわりに

プロジェクトの中でクライアントアプリの開発優先度が上がったことでフロントエンドの開発ポリシーを規定し、チームで開発が円滑に進められるように工夫して来ました。
Web管理画面の多くが、リスト画面 / 詳細画面 / 編集画面 が基本セットになっていることが多いので、今後はコードジェネレーターを活用してさらに効率よくUI実装ができるような仕込みを取り入れていくことも考えています。

Facebook

関連記事 | Related Posts

We are hiring!

【Woven City決済プラットフォーム構築 PoC担当バックエンドエンジニア(シニアクラス)】WovenPaymentSolution開発G/東京

WovenPaymentSolution開発グループについて私たちのグループはトヨタグループが取り組むWoven Cityプロジェクトの一部として、街の中で利用される決済システムの構築を行います。Woven Cityは未来の生活を実験するためのテストコースとしての街です。

【プロダクト開発バックエンドエンジニア】共通サービス開発G/東京・大阪

共通サービス開発グループについてWebサービスやモバイルアプリの開発において、必要となる共通機能=会員プラットフォームや決済プラットフォームの開発を手がけるグループです。KINTOの名前が付くサービスやTFS関連のサービスをひとつのアカウントで利用できるよう、様々な共通機能を構築することを目的としています。