mcpField
A
architecture-feature-first

architecture-feature-first

github.com/evanca開発663228 回インストール3 週間前に更新
リポジトリ

architecture-feature-first について

機能単位の構成とレイヤードアーキテクチャを用いる Flutter 向けスキルです。複数の状態管理方式に対応し、ロジックの配置、ファイル構成、依存関係の設定を示します。

主な機能

  1. 01機能単位のファイル構成
  2. 02UI・ロジック・データ層の境界
  3. 03状態管理方式に依存しない指針
  4. 04Repository と Service の責務
  5. 05依存性注入と機能追加の手順

活用シーン

  1. 01新しい Flutter アプリや機能の構成
  2. 02タイプ別構成から機能別構成へのリファクタリング
  3. 03ビジネスロジックの配置層を判断
  4. 04複雑なロジック向けのドメイン層追加

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/evanca/flutter-ai-rules --skill architecture-feature-first

インストール後、Claude Code で/architecture-feature-firstを入力して読み込まれたか確認します。

詳細

Flutter アーキテクチャ — フィーチャーファーストスキル

このスキルでは、推奨されるレイヤードアーキテクチャとフィーチャーファーストのファイル編成を使用して、Flutter アプリケーションを設計、構成、実装する方法を定義します。

状態管理方式に依存しません。UI レイヤーのビジネスロジックを担うコンポーネントは、選択した状態管理方式に応じて ViewModel、Controller、Cubit、Bloc、Provider、Notifier などと呼ばれます。アーキテクチャのルールは、いずれにも等しく適用されます。

使用する場面

次のような場合にこのスキルを使用します。

  • 新しい Flutter アプリや機能のフォルダー/ファイル構成を設計するとき。
  • 新しい View、ViewModel、Repository、Service を作成するとき。
  • あるロジックをどのレイヤーが担うかを決めるとき。
  • コンポーネント間の依存性注入を設定するとき。
  • 複雑なビジネスロジックのためにドメイン(ロジック)レイヤーを追加するとき。
  • 既存アプリの構成をタイプファーストからフィーチャーファーストにリファクタリングするとき。

1. レイヤー

すべてのアプリをUI レイヤーとデータレイヤーに分けます。複雑なアプリに限り、その間にロジック(ドメイン)レイヤーを追加します。

┌──────────────────────────────────────────────────────────────┐
│   UI Layer    │  Views + business logic holders              │
│               │  (ViewModel / Cubit / Controller / Provider) │
├──────────────────────────────────────────────────────────────┤
│  Logic Layer  │  Use Cases / Interactors  (optional)         │
├──────────────────────────────────────────────────────────────┤
│   Data Layer  │  Repositories + Services                     │
└──────────────────────────────────────────────────────────────┘

ルール:

  • 隣接するレイヤー間でのみ通信できます。UI レイヤーから Service に直接アクセスしてはいけません。
  • ロジックレイヤーを追加するのは、ビジネスロジックがビジネスロジックを担うコンポーネントに収まらないほど複雑な場合、または複数の画面で再利用される場合に限ります。
  • データの変更は常にデータレイヤー(SSOT = Repository)で行います。UI レイヤーやロジックレイヤーでは変更しません。
  • 単方向のデータフローに従います。状態は下方向(Data → UI)に流れ、イベントは上方向(UI → Data)に流れます。

2. フィーチャーファーストのファイル構成

コードはタイプ別ではなく、機能別に整理します。1 つの機能に属するすべてのレイヤーを、1 つのディレクトリにまとめます。

ディレクトリ構成の例
lib/
├── app.dart
├── main.dart
├── core/                          # Shared utilities, theme, DI setup
│   ├── di/
│   │   └── service_locator.dart
│   ├── theme/
│   │   └── app_theme.dart
│   └── network/
│       └── api_client.dart
├── features/
│   ├── auth/
│   │   ├── data/
│   │   │   ├── auth_repository.dart
│   │   │   └── auth_api_service.dart
│   │   ├── domain/                # Optional — only for complex logic
│   │   │   └── login_usecase.dart
│   │   └── ui/
│   │       ├── auth_viewmodel.dart
│   │       ├── login_screen.dart
│   │       └── widgets/
│   │           └── login_form.dart
│   └── profile/
│       ├── data/
│       │   ├── profile_repository.dart
│       │   └── profile_api_service.dart
│       └── ui/
│           ├── profile_viewmodel.dart
│           └── profile_screen.dart
└── shared/                        # Shared widgets, models, extensions
    ├── models/
    │   └── user.dart
    └── widgets/
        └── loading_indicator.dart

各機能ディレクトリには、その機能に必要なファイルを配置します。ファイル名は、選択した状態管理方式に応じて付けます。

方式ビジネスロジックを担うファイル
MVVM / ChangeNotifier*_viewmodel.dart / *_controller.dart
BLoC*_cubit.dart / *_bloc.dart
Provider / Riverpod*_provider.dart / *_notifier.dart

3. コンポーネントの責務

View
  • ユーザーへのデータの表示方法を記述します。ロジックは最小限にし、UI に関するものだけにします。
  • ユーザーの操作に応じて、イベントをビジネスロジックを担うコンポーネントに渡します。
  • 再利用可能なウィジェットは、widgets/ サブディレクトリ内の個別コンポーネントとして切り出します。
  • 可能な限り StatelessWidget を使用し、build メソッドはシンプルに保ちます。
ビジネスロジックを担うコンポーネント(ViewModel / Cubit / Controller / Provider)
  • アプリのデータを UI の状態に変換するロジックを持ち、View に必要な現在の状態を維持します。
  • View にコールバック(コマンド)を公開し、Repository からデータを取得して変換します。
class AuthViewModel extends ChangeNotifier {
  final AuthRepository _authRepo;
  AuthViewModel(this._authRepo);

  bool _isLoading = false;
  bool get isLoading => _isLoading;

  String? _error;
  String? get error => _error;

  Future<bool> login(String email, String password) async {
    _isLoading = true;
    _error = null;
    notifyListeners();
    try {
      await _authRepo.login(email, password);
      return true;
    } catch (e) {
      _error = e.toString();
      return false;
    } finally {
      _isLoading = false;
      notifyListeners();
    }
  }
}
Repository
  • 指定された種類のモデルデータに対する唯一の信頼できる情報源(SSOT)です。
  • データを変更できる唯一のクラスです。それ以外のすべてのクラスは、このクラスからデータを読み取ります。
  • キャッシュ、エラー処理、データ更新のロジックを担います。
  • Service からの生データをドメインモデルに変換します。
Service
  • API エンドポイントをラップし、非同期レスポンスオブジェクトを公開します。
  • データ読み込みを分離し、状態は持ちません。

4. ドメインレイヤー(ユースケース)

ユースケース/インタラクターを導入するのは、次の場合に限ります。

  • ロジックが複雑で、UI レイヤーまたはデータレイヤーに適切に収まらない場合。
  • ロジックが複数のビジネスロジックを担うコンポーネントで再利用される場合、または複数の Repository のデータを統合する場合。

シンプルな CRUD アプリにドメインレイヤーを追加しないでください。


5. 依存性注入

コンポーネントに依存関係を提供するために依存性注入を使用し、テスト容易性と柔軟性を高めます。

  • コンストラクターを通じて、ビジネスロジックを担うコンポーネントに Repository を渡します。
  • コンストラクターを通じて、Repository に Service を渡します。
  • 実装を利用側の変更なしに差し替えられるよう、抽象インターフェースを定義します。
// In service_locator.dart — register dependencies at startup
void setupDependencies() {
  final apiClient = ApiClient();

  // Services
  final authService = AuthApiService(apiClient);
  final profileService = ProfileApiService(apiClient);

  // Repositories
  final authRepo = AuthRepository(authService);
  final profileRepo = ProfileRepository(profileService);

  // Register with your DI framework (get_it, provider, riverpod, etc.)
  getIt.registerSingleton<AuthRepository>(authRepo);
  getIt.registerSingleton<ProfileRepository>(profileRepo);
}

6. ワークフロー:新機能の追加

  1. features/<name>/ ディレクトリを作成します。data/、ui/、必要に応じて domain/ サブディレクトリを作成します。
  2. サービスを実装します — API エンドポイントを data/<name>_api_service.dart でラップします。
  3. リポジトリを実装します — サービスを注入し、data/<name>_repository.dart でキャッシュとエラーハンドリングを追加します。
  4. ViewModel を実装します — リポジトリを注入し、ui/<name>_viewmodel.dart で UI の状態とコマンドを公開します。
  5. ビューを実装します — ViewModel にバインドし、状態を描画して、ui/<name>_screen.dart でイベントをディスパッチします。
  6. DI に登録します — 新しいサービス、リポジトリ、ViewModel をサービスロケーターに追加します。
  7. 確認します — ビューがサービスに直接アクセスしていないこと、およびデータが一方向に流れることを確認します。

参考資料

コメント