mcpField
A
architecture-feature-first

architecture-feature-first

github.com/evanca开发663228 次安装3 周前更新
代码仓库

关于 architecture-feature-first

用于按功能组织 Flutter 应用并采用分层架构的技能。它适配多种状态管理方式,并说明逻辑归属、文件结构和依赖配置规则。

主要功能

  1. 01按功能组织文件
  2. 02明确 UI、逻辑与数据层边界
  3. 03不绑定状态管理方案
  4. 04说明 Repository 与 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 架构 — Feature-First 技能

本技能定义了如何使用推荐的分层架构和以功能为先的文件组织方式来设计、组织和实现 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 层或逻辑层中不得进行变更。
  • 遵循单向数据流:状态向下流动(数据 → UI),事件向上流动(UI → 数据)。

2. Feature-First 文件结构

按功能而非类型组织代码。将属于同一功能的所有层归入同一个目录。

示例目录结构
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 相关逻辑。
  • 响应用户交互时,将事件传递给业务逻辑承载组件。
  • 将可复用的 Widget 提取为 widgets/ 子目录中的独立组件。
  • 尽可能使用 StatelessWidget;保持构建方法简洁。
业务逻辑承载组件(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. 实现 Service——在 data/<name>_api_service.dart 中封装 API 端点。
  3. 实现 Repository——注入 Service,并在 data/<name>_repository.dart 中添加缓存和错误处理。
  4. 实现 ViewModel——注入 Repository,并在 ui/<name>_viewmodel.dart 中公开 UI 状态和命令。
  5. 实现 View——绑定到 ViewModel、渲染状态,并在 ui/<name>_screen.dart 中分发事件。
  6. 在 DI 中注册——将新的 Service、Repository 和 ViewModel 添加到服务定位器。
  7. 验证——确认 View 从不直接访问 Service,且数据以单向方式流动。

参考资料

评论

支持 Markdown