鸿蒙大型项目架构高级复盘:从单体混乱架构到Clean Architecture分层演进/领域驱动设计/模块解耦全路径
·



一、前置思考
1.1 为什么架构会腐烂?
架构腐烂的五个信号:
→ 改一行代码要动五个文件 (耦合)
→ 没人说得清模块边界 (职责混乱)
→ 新功能越来越慢 (熵增)
→ 测试写不进去 (依赖地狱)
→ 新人上手要三个月 (认知负担)
腐烂的根源: 需求在变,架构没跟上
→ 架构不是画一次就完事,而是持续演进的过程
1.2 架构复盘的本质
复盘不是"重写"而是"重构":
→ 保持对外行为不变,逐步调整内部结构
→ 每一小步都可编译、可测试、可上线
→ 大爆炸式重写 = 高风险 + 业务停滞
演进路径: 单体混乱 → 分层 → 模块化 → Clean Architecture
二、核心原理
2.1 三层分层架构
┌──────────────────────────────┐
│ Presentation 表现层 │ ← UI/页面/组件
│ @Component / @Builder │
├──────────────────────────────┤
│ Domain 领域层 │ ← 业务规则/用例
│ Entity / UseCase / 领域服务 │
├──────────────────────────────┤
│ Data 数据层 │ ← 数据来源
│ Repository / 本地/远程实现 │
└──────────────────────────────┘
依赖方向: 外层依赖内层,内层不依赖外层
→ 表现层依赖领域层
→ 领域层依赖数据层的"接口"而非实现
2.2 Clean Architecture 五层
┌─────────────────────────────┐
│ Entities 企业业务实体 │
├─────────────────────────────┤
│ Use Cases 应用用例 │
├─────────────────────────────┤
│ Interface Adapters 接口适配器 │ ← 控制器/网关
├─────────────────────────────┤
│ Frameworks 框架/驱动 │ ← ArkUI/数据库/网络
└─────────────────────────────┘
依赖规则: 只向内依赖,外层变化不影响内层
→ 换数据库/换 UI 框架,业务核心代码不动
2.3 DDD 领域驱动设计核心
战术设计要素:
Entity: 有唯一标识、有生命周期的对象 (订单)
Value Object: 无标识、不可变的值 (金额)
Aggregate: 聚合根 + 一致性边界 (订单+订单项)
Repository: 聚合的存储接口
Domain Service: 跨聚合的业务逻辑
战略设计要素:
Bounded Context: 领域边界 (订单域/库存域)
Context Map: 上下文之间的集成关系
Ubiquitous Language: 统一语言 (业务与代码同词)
2.4 模块解耦手段
1. 依赖倒置 (DIP): 依赖抽象不依赖具体
2. 接口隔离: 每个模块只暴露最小接口
3. 事件解耦: 模块间用事件通信而非直接调用
4. 路由解耦: 页面跳转走路由表,不直接 import
5. 数据模型隔离: 各层用自己的模型,禁止直接透传
三、源码/API 深度解析
3.1 分层目录结构落地
entry/src/main/ets/
├── pages/ # Presentation: 页面 (薄层)
├── components/ # 表现层组件
├── domain/ # 领域层
│ ├── entities/ # 业务实体
│ ├── usecases/ # 用例
│ └── repositories/ # 仓储接口 (抽象)
├── data/ # 数据层
│ ├── repositories/ # 仓储实现
│ ├── sources/ # 本地/远程数据源
│ └── models/ # 数据模型 (DTO)
└── di/ # 依赖注入/组装根
关键: 依赖箭头永远指向 domain,domain 不 import 任何框架
3.2 依赖倒置示例
// domain/repositories/UserRepository.ets —— 接口 (依赖方向: 向外)
export interface UserRepository {
getUser(id: string): Promise<UserEntity>;
saveUser(user: UserEntity): Promise<void>;
}
// data/repositories/UserRepositoryImpl.ets —— 实现
import { UserRepository } from '../domain/repositories/UserRepository';
import { rdb } from '@kit.ArkData';
export class UserRepositoryImpl implements UserRepository {
async getUser(id: string): Promise<UserEntity> {
// RDB 实现
const row = await queryUserFromDb(id);
return rowToEntity(row);
}
}
// 表现层只依赖接口:
// const repo: UserRepository = di.get<UserRepository>();
// → 换实现 (缓存/网络/数据库) 只改 di 组装,不动业务
3.3 用例层封装业务规则
// domain/usecases/PlaceOrderUseCase.ets
export class PlaceOrderUseCase {
constructor(private orderRepo: OrderRepository,
private stockRepo: StockRepository) {}
async execute(userId: string, items: OrderItem[]): Promise<OrderEntity> {
// 1. 校验库存
for (const it of items) {
const stock = await this.stockRepo.getStock(it.skuId);
if (stock < it.quantity) {
throw new Error('库存不足: ' + it.skuId);
}
}
// 2. 创建订单 (聚合根)
const order = OrderEntity.create(userId, items);
// 3. 持久化
await this.orderRepo.save(order);
return order;
}
}
// 业务规则在领域层,UI/数据库怎么变都不影响它
四、企业级实战落地
4.1 演进路径对照
| 阶段 | 特征 | 问题 | 演进动作 |
|---|---|---|---|
| 单体混乱 | 所有代码一个页面 | 改一处全崩 | 先分三层 |
| 三层分层 | UI/业务/数据分离 | 层内仍耦合 | 按业务切模块 |
| 模块化 | 按业务域拆分 | 模块间强依赖 | 接口化 + 事件解耦 |
| Clean | 依赖向内 | 组装复杂 | 引入 DI 容器 |
4.2 完整示例:架构演进演示
@Entry
@ComponentV2
struct ArchitectureRefactorDemo {
@Local stage: string = '待开始';
@Local metrics: string[] = [];
@Local logs: string[] = [];
private runEvolution(): void {
this.logs = [];
this.stage = '单体混乱';
this.metrics = [];
this.log('🏚️ 阶段1: 单体混乱架构');
this.log(' 全部逻辑在页面里: 网络+缓存+业务+UI 混写');
this.log(' 指标: 单文件 3000 行 · 无法单测');
this.stage = '三层分层';
this.log('🏗️ 阶段2: 三层分层');
this.log(' pages/components | domain | data 分离');
this.log(' 业务规则抽到 UseCase,可独立测试');
this.log(' 指标: 耦合度下降 60% · 单测覆盖率 70%');
this.stage = '模块化';
this.log('🧩 阶段3: 模块化拆分');
this.log(' 按业务域: order/pay/user/stock 独立模块');
this.log(' 模块间用接口 + 事件通信');
this.log(' 指标: 模块独立构建 · 并行开发');
this.stage = 'Clean';
this.log('✨ 阶段4: Clean Architecture');
this.log(' domain 零框架依赖,可整体迁移');
this.log(' 换 UI/换数据库不动业务核心');
this.log(' 指标: 技术债清除 · 新功能提速 3 倍');
}
build() {
Column({ space: 12 }) {
Text('🏛️ 架构演进复盘演示').fontSize(20).fontWeight(FontWeight.Bold)
Text('阶段: ' + this.stage).fontSize(13).fontColor('#0969DA')
Row({ space: 8 }) {
Button('▶ 模拟架构演进').layoutWeight(1).height(40).fontSize(12)
.onClick(() => this.runEvolution())
Button('清空').height(40).fontSize(12)
.onClick(() => this.logs = [])
}
.width('100%')
Scroll() {
Column() {
ForEach(this.logs, (l: string) => {
Text(l).fontSize(11).lineHeight(18).fontColor('#24292F').width('100%')
}, (l: string, i: number) => l + i)
}.width('100%')
}
.layoutWeight(1).width('100%').scrollBar(BarState.Off)
}
.width('100%').height('100%').padding(16)
.backgroundColor('#F6F8FA')
}
}
4.3 架构复盘清单
1. 画现状依赖图: 找出环状依赖和上帝类
2. 定义目标边界: 按业务域划分,不按技术分层硬切
3. 小步重构: 一次移动一个模块,保持可编译
4. 用测试兜底: 重构前写行为测试,重构后验证
5. 持续演进: 架构评审纳入常规流程,防止再次腐烂
五、问题排查与性能优化
| 问题 | 原因 | 解决 |
|---|---|---|
| 重构后功能回归 | 行为测试缺失 | 先写契约测试再重构 |
| 分层后性能下降 | 过度抽象/透传 | 批量接口 + 避免逐层代理 |
| 模块间循环依赖 | 边界不清 | 依赖图分析 + 接口下沉 |
| DDD 过度设计 | 无脑套概念 | 从价值高的核心域开始 |
| 事件风暴难排错 | 链路不可观测 | 事件链路 ID 透传 |
| 重构拖太久 | 步子太大 | 持续集成 + 小步合入 |
5.1 架构质量度量
1. 耦合度: 模块间依赖数量 (目标: 低)
2. 内聚度: 模块内职责相关性 (目标: 高)
3. 测试性: 核心逻辑单测覆盖率
4. 变更影响面: 改一个需求动的文件数
5. 交付速度: 新功能平均开发时长 (架构回报的终极指标)
六、高阶总结与最佳实践
- 架构是演进的:从单体到 Clean 是渐进过程,每步可编译可上线。
- 依赖向内:domain 不碰框架,是 Clean Architecture 的灵魂。
- 模块按业务切:不是按技术分层切,边界要贴合业务变化。
- 测试兜底重构:没有行为测试的重构是赌博。
- 复盘常态化:架构评审进流程,防止熵增回归。
一句话记住:架构复盘 = 承认腐烂 → 分层解耦 → 领域建模 → Clean 演进,小步快跑、测试兜底、依赖向内,让架构跟上业务的呼吸。
更多推荐



所有评论(0)