用途
VGV 分层架构为 Flutter 应用提供一套分层 monorepo 架构规范,把每个功能拆分为四个层次,并以独立 Dart 包和严格单向依赖来组织代码,从而保证边界清晰、可独立测试、数据包可复用。
主要能力与核心标准
- 四层结构:数据(Data)、仓库(Repository)、业务逻辑(Business Logic)、表现(Presentation),每个功能都恰好跨越这四层。
- 单向依赖:表现层 → 业务逻辑层 → 仓库层 → 数据层,不得跳过或反转。
- 数据层与仓库层位于 packages/ 目录,各自是独立 Dart 包,拥有自己的 pubspec.yaml。
- 业务逻辑层与表现层位于 lib/ 目录,按功能组织。
- 数据层包不包含任何领域/业务逻辑,必须能在无关项目中复用。
- 仓库之间不得互相依赖,仓库永不导入其他仓库。
- 数据包与仓库包不引入 Flutter SDK,使用 very_good_cli MCP server 的 create dart_package 工具脚手架生成。
- 每个领域一个仓库,例如 user_repository、weather_repository、auth_repository。
- 本地包使用路径依赖,同一仓库内的包不得使用 git: 或 pub 版本引用。
- 每个包边界使用 barrel 导出,消费者不得直接导入 src/。
- 仓库通过构造函数注入接收数据层依赖,不得在内部实例化客户端。
- 应用启动时装配所有层:main_<flavor>.dart 创建客户端与仓库,并通过 RepositoryProvider 提供。
各层职责与位置
- 数据层:负责外部通信,包括 API 调用、本地存储、平台插件;位于 packages/<name>_api_client/;仅依赖外部包;示例 user_api_client、local_storage_client。
- 仓库层:负责数据编排,组合数据源、转换模型、缓存;位于 packages/<name>_repository/;依赖数据层包;示例 user_repository、weather_repository。
- 业务逻辑层:负责状态管理,处理用户操作并发出状态变化;位于 lib/<feature>/bloc/ 或 lib/<feature>/cubit/;依赖仓库层;示例 LoginBloc、ProfileCubit。
- 表现层:负责 UI,包括 widgets、pages、views、layout;位于 lib/<feature>/view/;依赖业务逻辑层;示例 LoginPage、ProfileView。
使用方式
数据层
数据层处理所有外部通信。每个数据包封装单一外部来源(REST API、本地数据库、平台插件),暴露类型化方法和响应模型。规则包括:模型精确匹配 API/存储结构;不导入 Flutter;通过构造函数注入 HTTP 客户端以便测试;响应模型使用 fromJson / toJson 工厂;通过 barrel 文件导出,不暴露 src/。
仓库层
仓库层编排数据源并暴露领域模型。每个仓库组合一个或多个数据客户端,把响应模型转换为领域模型,为业务逻辑层提供干净 API。规则包括:仓库之间无依赖;不引入 Flutter SDK;领域模型位于仓库包而非数据包;把数据模型转换为领域模型,不向上游泄漏 API 响应结构;所有数据客户端通过构造函数注入。
依赖图
各层通过 pubspec.yaml 中的路径依赖强制架构约束。数据包仅依赖外部包;仓库包通过路径依赖数据层包;根应用只依赖仓库包,数据包作为传递依赖。应用永不直接依赖数据包,从而强制层边界,使业务逻辑与表现层无法绕过仓库层。
数据流
以用户点击“Load Profile”按钮为例:表现层派发事件(context.read<ProfileBloc>().add(ProfileLoadRequested(userId: '123')));业务逻辑调用仓库(Bloc 处理器调用 _userRepository.getUser(event.userId) 并根据结果发出状态);仓库调用数据客户端(UserRepository.getUser 委托给 _userApiClient.getUser 并把响应转换为领域 User);数据层与外部来源通信(UserApiClient.getUser 发起 HTTP 请求并返回类型化 UserResponse);数据向上回流,表现层通过 BlocBuilder 根据新状态重建。
应用启动
main_<flavor>.dart 创建所有数据客户端与仓库,然后传给 App widget。MultiRepositoryProvider 使仓库对整个 widget 树可用。不同 flavor 只改变配置(base URL、API key),架构在开发、预发、生产环境保持一致。
适用场景
适用于需要清晰分层边界、可独立测试、数据包可跨项目复用的 Flutter 应用,尤其是采用 monorepo 组织、需要多 flavor 配置、并希望以 Bloc/Cubit 管理状态的项目。
限制与反模式
- Widget 直接调用 API 客户端会绕过仓库与业务逻辑层,正确做法是 Widget 派发事件 → Bloc 调用仓库 → 仓库调用 API 客户端。
- 仓库导入另一个仓库会形成循环或纠缠依赖,破坏独立可测性;每个仓库应自包含,必要时在 Bloc 层组合数据。
- 领域模型放在数据层会把外部 API 结构与内部领域耦合;数据层放响应模型,仓库层放领域模型并做转换。
- 业务逻辑放在仓库会使仓库变成不可测试的巨石;仓库负责转换数据,Bloc/Cubit 包含所有业务规则。
- 本地包使用 git: 或 pub 版本会破坏 monorepo;应使用 path: 依赖。
- 数据/仓库包引入 Flutter 会阻止其在纯 Dart 环境(CLI 工具、服务器)中使用;应使用 create dart_package 工具脚手架,不依赖 Flutter SDK。
- 一个巨型仓库承担过多职责,无法独立测试;应按领域边界拆分,如 user_repository、settings_repository。
- 直接导入 src/ 会破坏封装;应通过 barrel 文件导出公共 API,导入包而非 src/ 路径。
常见工作流
- 新增数据源:用 create dart_package 工具脚手架生成 <name>_api_client --output-directory packages;在 pubspec.yaml 添加外部依赖;在 lib/src/models/ 创建带 fromJson/toJson 的响应模型;创建 barrel 文件;实现客户端类;创建包 barrel 文件;在 test/ 编写镜像 lib/ 结构的单元测试;使用 very_good_cli MCP server 的 test 工具并传入 directory: 'packages/<name>_api_client' 限定运行范围。
- 新增仓库:脚手架生成 <name>_repository --output-directory packages;在 pubspec.yaml 添加对数据层包的路径依赖;添加 equatable 依赖;在 lib/src/models/ 创建继承 Equatable 的领域模型;创建 barrel 文件;实现带构造函数注入数据客户端的仓库类;添加响应模型到领域模型的转换逻辑;创建包 barrel 文件;使用模拟数据客户端编写单元测试。
- 把仓库接入功能:在根 pubspec.yaml 添加对仓库包的路径依赖;在 main_<flavor>.dart 创建仓库并传给 App;在 App 的 MultiRepositoryProvider 中添加 RepositoryProvider.value;创建注入仓库的 Bloc/Cubit;用 BlocProvider 和 BlocBuilder 构建 Page/View。
暂未提供
关于收费、价格、平台支持范围、性能效果等具体信息,资料中暂未提供。