Skip to content

Instantly share code, notes, and snippets.

@mono0926
Last active April 24, 2026 01:25
Show Gist options
  • Select an option

  • Save mono0926/e47bd42a260b9947877db854b4f9b350 to your computer and use it in GitHub Desktop.

Select an option

Save mono0926/e47bd42a260b9947877db854b4f9b350 to your computer and use it in GitHub Desktop.
スキーマ集約型ワークスペース・アーキテクチャ

Cloud Functionsまで含めてDartに統一するFlutterアプリのアーキテクチャーについて、AIに相談した結果メモ(現時点では未検証):


🎯 最終形態:スキーマ集約型ワークスペース・アーキテクチャ

Dart 3.5の Dart Workspaces をインフラの主軸とし、バックエンド(Cloud Functions)には公式の firebase_admin_sdk を採用。フロントエンドの「Feature-first(機能駆動)」と、バックエンドの「データグラフの集約」を完璧に両立させる構成です。

📁 ディレクトリ構成図

無駄なパッケージ分割による pubspec.yaml の乱造を防ぎ、開発者が迷わずコードを読み書きできる究極に身軽な構成です。

my_project_workspace/
├── pubspec.yaml                # ワークスペース定義 (resolution: workspace)
│
├── apps/
│   ├── main/                   # 📱 メインアプリ (Flutter)
│   │   ├── pubspec.yaml        # 依存: shared_schema, shared_ui, riverpod 等
│   │   └── lib/
│   │       ├── core/           # アプリ固有の基盤 (go_router, DI設定など)
│   │       └── features/       # 【機能分割】 auth, feed, user 等 (UI/状態管理)
│   │
│   └── admin/                  # 💻 管理アプリ (Flutter Web等)
│       ├── pubspec.yaml        # 依存: shared_schema, shared_ui, riverpod 等
│       └── lib/
│           ├── core/
│           └── features/       # 【機能分割】 auth, user_management 等
│
├── functions/                  # ☁️ Cloud Functions (Pure Dart)
│   ├── pubspec.yaml            # 依存: shared_schema, firebase_admin_sdk
│   └── lib/
│       └── ...                 # エントリーポイントとトリガーロジック
│
└── packages/
    ├── shared_schema/          # 🟢 【Pure Dart】 全モデル・定数・ドメインロジック
    │   ├── pubspec.yaml        # 依存: freezed, json_serializable のみ
    │   └── lib/
    │       ├── core/           # 静的レゾルバ (TimestampResolver 等)
    │       └── features/       # auth, feed 等のデータモデル・バリデーション定義
    │
    └── shared_ui/              # 🔵 【Flutter】 真に共通なデザインシステム
        ├── pubspec.yaml        # 依存: flutter
        └── lib/                # 共通テーマ、汎用ボタン、ダイアログ等

🏛️ 3つの設計思想と解決策(The "Why" and "How")

1. 依存関係の解決:Dart Workspacesの採用

サードパーティのMelosによる依存関係のリンクを廃止し、Dart標準の機能でモノレポを構築します。親の pubspec.yaml で対象ディレクトリを宣言し、各子パッケージで resolution: workspace を指定することで、バージョン衝突を根本から防ぎます。 build_runner の並列実行などのタスクランナー用途としてのみ、限定的にMelosやスクリプトを併用します。

2. DXと堅牢性の両立:機能分割のハイブリッド・アプローチ

  • データ(Domain)は集約: Functionsが複数機能をまたぐトランザクション処理を行う際、依存地獄に陥らないよう、すべてのデータモデルは shared_schema 1つに集約します。これにより Functions への dart:ui 誤混入も物理的に防げます。
  • UI/状態は分散: riverpod やUIを含むコードは、パッケージ分割の手間を省くため apps/main などの内部で features/ としてフォルダ分割します。コンテキストスイッチを最小限に抑えます。

3. 型不一致問題の正攻法:静的レゾルバパターン

cloud_firestorefirebase_admin_sdkTimestamp 型の衝突を、共通モデルを汚染せずに解決します。

① 共通パッケージ (shared_schema) の定義

モデル側はSDKに依存せず、アノテーションのみを使用します。

// packages/shared_schema/lib/core/timestamp_resolver.dart
import 'package:json_annotation/json_annotation.dart';

class TimestampResolver {
  // 初期値は安全なDateTime変換
  static DateTime Function(dynamic json) fromJson = (json) => DateTime.parse(json as String);
  static dynamic Function(DateTime date) toJson = (date) => date.toIso8601String();
}

class SharedTimestampConverter implements JsonConverter<DateTime, dynamic> {
  const SharedTimestampConverter();
  @override DateTime fromJson(dynamic json) => TimestampResolver.fromJson(json);
  @override dynamic toJson(DateTime date) => TimestampResolver.toJson(date);
}

// packages/shared_schema/lib/features/user/user_profile.dart
@freezed
class UserProfile with _$UserProfile {
  const factory UserProfile({
    required String name,
    @SharedTimestampConverter() required DateTime createdAt,
  }) = _UserProfile;
  factory UserProfile.fromJson(Map<String, dynamic> json) => _$UserProfileFromJson(json);
}

② アプリ側 (apps/main) での注入

アプリ起動時に cloud_firestore のロジックを注入します。

// apps/main/lib/main.dart
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:shared_schema/shared_schema.dart';

void main() {
  TimestampResolver.fromJson = (json) => (json as Timestamp).toDate();
  TimestampResolver.toJson = (date) => Timestamp.fromDate(date);
  
  runApp(const ProviderScope(child: MyApp()));
}

③ Functions側 (functions) での注入

サーバー起動時に firebase_admin_sdk のロジックを注入します。

// functions/lib/main.dart
import 'package:firebase_admin_sdk/firebase_admin_sdk.dart';
import 'package:shared_schema/shared_schema.dart';

void main() {
  TimestampResolver.fromJson = (json) => (json as Timestamp).toDate();
  TimestampResolver.toJson = (date) => Timestamp.fromDate(date);

  final admin = FirebaseAdminApp.initializeApp(projectId: 'your-project-id');
  // ... routing to specific functions
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment