Files
conti-docs/01-project-structure.md
T

186 lines
9.5 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 01. 工程结构 / 分包策略
## 决策
使用 **[Melos](https://melos.invertase.dev/) monorepo**,按 **feature** 拆分成独立 Dart package,而不是单一 Flutter package 内部用文件夹分层。
## 包结构总览
```
conti-app/
melos.yaml
app/ # 壳工程:唯一的 Flutter application,负责路由汇总、DI 装配、编译出 ipa/apk
packages/
core_ui/ # 通用组件、主题、设计 token
core_network/ # dio 封装、拦截器、统一异常
core_storage/ # 本地存储抽象(Drift/secure storage 封装)
core_auth/ # 登录态、token 管理
core_router/ # 路由聚合、公共 route guard
feature_scan/ # 原「扫码」小程序
feature_payment/ # 原「支付」小程序
feature_store/ # 原「门店」小程序
feature_.../
native_scan/ # 原生插件包:扫码(android/ios/ohos 三端实现)
native_payment/
native_bluetooth/
```
## 依赖规则(编译期强制边界,是这套结构的核心价值)
- `app` 可以依赖所有 `core_*``feature_*`
- `feature_*` **只能**依赖 `core_*`**不能**相互依赖(`feature_payment``pubspec.yaml` 里不允许出现 `feature_store` 的 path dependency)。
- `core_*` 之间尽量不互相依赖;唯一允许的例外是 `core_network` 依赖 `core_auth`(取 token 做请求签名/刷新)。
- `feature_*` 可以依赖对应的 `native_*` 包(如 `feature_scan` 依赖 `native_scan`)。
- `native_*` 只依赖 Flutter SDK 和 [Pigeon](https://pub.dev/packages/pigeon) 生成的代码,不依赖任何 `core_*` / `feature_*`——保证原生插件包可以脱离业务单独编译、单独测试(详见 [07-native-integration.md](./07-native-integration.md))。
这些规则由 Dart 的包依赖机制**物理强制**:`feature_a` 根本 import 不到 `feature_b` 的任何符号,不是靠代码规范或 review 口头约束。
## Feature 间通信怎么处理
这是最容易被绕开、也是这套边界能否守住的关键点,必须写清楚合法方式:
1. **路由跳转 + 可序列化参数**(多数场景)——比如从 `feature_store` 跳到 `feature_payment`,通过 `core_router` 声明的路径 + query/extra 参数传递,不直接引用对方的 Dart 类型。
2. **通过 `core_*` 定义的抽象接口 + DI 注册实现**——真正需要跨 feature 拿数据或发通知的场景(比如支付完成后要清空购物车),在某个 `core_*` 包里定义接口,各 feature 各自实现并在 `app` 层注册,调用方只依赖 `core_*` 里的抽象类型。
**不允许**的做法:任何 `feature_*``pubspec.yaml` 里直接 path dependency 另一个 `feature_*`,哪怕只是想复用一个 widget——这种情况应该把这个 widget 提到 `core_ui`
## 命名规范
- `core_xxx`:基础设施层,不含具体业务逻辑。
- `feature_xxx`:对应一个原小程序/业务域。
- `native_xxx`:原生能力插件包,内部含 `android/``ios/``ohos/` 三套原生实现目录。
## Melos 配置示例(8.x,基于 Dart Pub Workspaces
Melos 7.0 起改用 Dart 官方原生的 **[Pub Workspaces](https://dart.dev/tools/pub/workspaces)** 机制,不再有独立的 `melos.yaml` 文件,配置写进根目录 `pubspec.yaml`;每个子包的 `pubspec.yaml` 需要加 `resolution: workspace`。要求 **Dart SDK ≥ 3.6.0**(我们的基线 Flutter 3.44.8 对应 Dart 3.12.2,满足要求)。
根目录 `pubspec.yaml`
```yaml
name: conti_app
publish_to: none
environment:
sdk: ^3.12.0
workspace:
- app
- packages/core_ui
- packages/core_network
- packages/core_storage
- packages/core_auth
- packages/core_router
- packages/feature_scan
- packages/feature_payment
- packages/feature_store
- packages/native_scan
- packages/native_payment
- packages/native_bluetooth
dev_dependencies:
melos: ^8.2.2
melos:
scripts:
analyze:
run: melos exec -- flutter analyze
test:
run: melos exec -- flutter test
format:
run: melos exec -- dart format --set-exit-if-changed .
```
每个子包(比如 `packages/feature_scan/pubspec.yaml`):
```yaml
name: feature_scan
resolution: workspace
dependencies:
core_ui:
path: ../core_ui
core_network:
path: ../core_network
```
## 新增 feature 包的标准脚手架
```
feature_xxx/
pubspec.yaml # resolution: workspace + 依赖 core_ui / core_network / core_router 等,不依赖其他 feature
lib/
feature_xxx.dart # 唯一对外导出文件(barrel file):只暴露路由注册函数和必要的 public widget
src/
presentation/
domain/ # 可选,见下方分层规范文档
data/
test/
```
`src/` 目录下的内容视为包内私有实现,只有 `feature_xxx.dart` 这一个文件是对外契约——这条靠 code review 检查,Dart 语言本身没有强制的 package-private 关键字。`domain/` 目录的取舍规则详见 [02-layering.md](./02-layering.md)(待写)。
## 版本管理
不发布到 pub.dev,全部用 melos 的 path dependency,包版本号跟随 `app` 的整体版本号统一管理(fixed versioning),不做 melos 的 independent versioning——没有对外发布需求,独立版本号只会增加维护负担。
## 附录:Melos 是什么,日常怎么用
给没接触过 Dart 多包仓库工具的同学看的入门说明。
### 要解决的问题
Dart 官方的包管理工具 `pub` 天生只认"一个 `pubspec.yaml` = 一个包"。如果要在同一个 git 仓库里维护多个互相依赖的私有包(比如 `app` 依赖 `feature_scan``feature_scan` 依赖 `core_network`),原生 pub 只支持手动在每个包的 `pubspec.yaml` 里写 `path: ../../packages/core_network` 这种相对路径依赖——能跑,但没有任何批量操作能力:想给所有包统一跑一次 `flutter analyze``flutter test`,或者统一升级某个第三方库版本,都得一个包一个包手动进去执行。
**Melos 就是给这种多包仓库提供批量管理能力的工具**,类似 JS 生态里的 [Lerna](https://lerna.js.org/)/Nx,只不过是 Dart/Flutter 版本。它不改变 Dart 语言或 pub 本身的机制,只是在多个包外面包一层"批处理脚本 + 配置"。
### 核心概念
1. **根目录 `pubspec.yaml` 里的 `workspace:` 字段 + `melos:` 配置块**8.x 版本不再有独立的 `melos.yaml` 文件(7.0 之前是独立文件,现已合并进 Dart 官方原生的 Pub Workspaces 机制)。`workspace:` 列出所有子包路径,`melos:` 块下的 `scripts:` 定义可复用脚本(见上文示例)。要求 Dart SDK ≥ 3.6.0。
2. **`melos bootstrap`**(简写 `melos bs`):一键解析 workspace 内所有包之间的依赖关系——自动生成 `pubspec_overrides.yaml`,把 `feature_scan` 依赖 `core_network` 这种关系用本地路径链接起来,不用手写相对路径,也不需要真的发布到 pub.dev 才能互相依赖。**新人拉下代码后第一步永远是跑这个命令**。
3. **`melos exec`**:在每一个包目录下依次/并行执行同一条命令,比如 `melos exec -- flutter test` 就是把所有包都跑一遍测试,替代手动 `cd packages/feature_scan && flutter test && cd ../feature_payment && ...`
4. **`melos run <script-name>`**:调用 `melos.yaml` 里预定义的脚本别名(比如上文的 `melos run test`),团队里统一敲固定命令,不用记 `exec` 的完整写法。
### 日常开发流程(拿本仓库举例)
```bash
# 1. 第一次拉代码,或者别人加了新包/新依赖之后
melos bootstrap
# 2. 正常改代码,比如在 feature_scan 里改一个页面
cd packages/feature_scan
flutter run # 这一步跟平时写单个 Flutter 项目完全一样,感觉不到 melos 的存在
# 3. 提交前,跑一遍全仓库检查
melos run analyze
melos run test
# 4. 新增了 feature 包,或者某个包新增了对另一个包的依赖之后
melos bootstrap # 重新解析依赖关系
```
**关键体感**:平时在某一个包里写代码、`flutter run`、热重载,跟没有 melos 时完全一样——melos 只在"跨包操作"(装依赖、批量测试、批量分析)时才会用到,不侵入日常单包开发的手感。
### 常见的坑
- 加了新包,或改了某个包的依赖之后忘记跑 `melos bootstrap`,会出现"明明加了依赖但 import 不到"的报错——看到这个报错先跑一遍 bootstrap 再排查。
- 8.x 基于 Pub Workspaces 后,正常的包间链接**不再**依赖 `pubspec_overrides.yaml`(这是 7.0 之前版本的机制);只有配置了额外的 `dependencyOverridePaths`(用于覆盖外部第三方依赖,不是本仓库包之间的常规场景)时才会生成这个文件。如果看到这个文件出现却不记得配置过覆盖路径,说明配置可能有误,需要检查。
### 安装
```bash
dart pub global activate melos
```
全局命令,装一次即可,不需要每个项目单独安装。
## 参考链接
- [Melos 官方文档](https://melos.invertase.dev/)
- [melos | Dart package (pub.dev)](https://pub.dev/packages/melos)
- [Melos changelog](https://pub.dev/packages/melos/changelog)
- [Melos Configuration overview](https://melos.invertase.dev/configuration/overview)
- [Dart Pub Workspaces 官方文档](https://dart.dev/tools/pub/workspaces)
- [Pigeon | Dart package](https://pub.dev/packages/pigeon)
- [Drift | Dart package](https://pub.dev/packages/drift)
- [LernaJS 生态对标工具)](https://lerna.js.org/)