🤔什么是干净架构?
干净架构(Clean Architecture)是由Robert C. Martin(又称Uncle Bob)提出的一种软件设计理念。
它强调将业务逻辑与框架、UI和基础设施分离,使系统更易于维护、测试和适应变化。
在Express(后端)和Vue(前端)的全栈开发中应用【干净架构】,可以使你的应用更加健壮【所谓健壮性:比如用户乱输入、网络抽风、数据异常时,程序能妥善处理而不是直接死掉】、可测试和可维护。
干净架构的核心原则
- 独立于框架:不依赖任何特定框架的特性,所以泛用性会比较高
- 可测试:业务逻辑可以在没有UI、数据库或外部服务的情况下测试
- 独立于UI:UI可以轻松更改而不影响业务逻辑
- 独立于数据库:可以轻松切换数据库实现
- 独立于任何外部服务:业务逻辑不知道外部服务的实现细节
🧩干净架构的层次结构【重要】
1. 实体层(Entities)
- 定义:包含核心业务逻辑和实体
- Express示例:领域模型(如User、Product等)
- Vue示例:前端的数据模型定义
2. 用例层(Use Cases)
- 定义:包含应用特定的业务规则,协调数据流向/流出实体层
- Express示例:处理特定业务逻辑的服务(如UserService、OrderService)
- Vue示例:处理复杂组件逻辑的服务
3. 接口适配器层(Interface Adapters)
- 定义:将数据从用例和实体转换为外部系统(如数据库或Web)所需的格式
- Express示例:控制器、路由、数据库仓库实现
- Vue示例:API客户端、状态管理、组件props适配器
4. 框架和驱动层(Frameworks & Drivers)
- 定义:外部工具和框架的具体实现
- Express示例:Express本身、数据库驱动、外部API调用
- Vue示例:Vue框架、UI库、浏览器API
Express后端中的干净架构实现
典型目录结构
src/
├── domain/ # 实体层
│ ├── entities/ # 业务实体
│ └── use-cases/ # 用例层
├── infrastructure/ # 框架和驱动层
│ ├── config/ # 配置
│ ├── db/ # 数据库相关
│ └── server/ # Express服务器配置
├── interfaces/ # 接口适配器层
│ ├── controllers/ # 控制器
│ ├── repositories/# 仓储接口实现
│ └── routes/ # 路由
└── shared/ # 共享代码
下面讲一下上面的这个项目结构是如何运作的
调用流程示例(用户注册):
- HTTP请求到达
routes/user.ts→ 转发给controllers/UserController.create(); - 控制器解析请求体 → 调用
domain/use-cases/CreateUserUseCase; - 用例验证业务规则 → 通过
interfaces/repositories/UserRepository(接口)保存用户; 【注:use-cases(用例) 存放具体业务逻辑(如用户注册、订单创建),负责协调数据流(调用实体验证规则→通过仓储接口保存数据),是连接实体层与外部适配器的核心业务操作单元。】 - 仓库实现(如
infrastructure/db/mongoose-user-repo)操作数据库 → 结果逐层返回至客户端。
核心特点:
- 每层职责单一(路由只转发、控制器只适配、用例只处理业务、仓库只管数据库);
- 依赖单向流动(外层依赖内层,内层不感知外层,所以外层变化,内层不care)。
代码示例
实体层 - domain/entities/user.entity.ts
export class User {
constructor(
public readonly id: string,
public readonly name: string,
public readonly email: string
) {}
// 业务逻辑方法
isValid() {
return this.email.includes('@');
}
}
用例层 - domain/use-cases/create-user.usecase.ts
import { User } from '../entities/user.entity';
import { UserRepository } from '../../interfaces/repositories/user.repository';
export class CreateUserUseCase {
constructor(private readonly userRepository: UserRepository) {}
async execute(name: string, email: string): Promise<User> {
const user = new User(this.userRepository.nextId(), name, email);
if (!user.isValid()) {
throw new Error('Invalid user data');
}
return this.userRepository.create(user);
}
}
接口适配器层 - interfaces/controllers/user.controller.ts
import { Request, Response } from 'express';
import { CreateUserUseCase } from '../../domain/use-cases/create-user.usecase';
export class UserController {
constructor(private readonly createUserUseCase: CreateUserUseCase) {}
async createUser(req: Request, res: Response) {
try {
const { name, email } = req.body;
const user = await this.createUserUseCase.execute(name, email);
res.status(201).json(user);
} catch (error) {
res.status(400).json({ error: error.message });
}
}
}
Vue前端中的干净架构实现
典型目录结构
src/
├── core/ # 实体层和用例层
│ ├── entities/ # 业务实体
│ └── use-cases/ # 业务逻辑
├── infrastructure/ # 框架和驱动层
│ ├── api/ # API客户端
│ └── plugins/ # Vue插件
├── interfaces/ # 接口适配器层
│ ├── adapters/ # 数据适配器
│ ├── stores/ # 状态管理
│ └── views/ # 视图组件
└── shared/ # 共享代码
代码示例
实体层 - core/entities/user.entity.ts
export class User {
constructor(
public readonly id: string,
public readonly name: string,
public readonly email: string
) {}
}
用例层 - core/use-cases/user.usecase.ts
import { User } from '../entities/user.entity';
import { UserApi } from '../../infrastructure/api/user-api';
export class UserUseCase {
constructor(private readonly userApi: UserApi) {}
async createUser(name: string, email: string): Promise<User> {
return this.userApi.createUser(name, email);
}
async getUsers(): Promise<User[]> {
return this.userApi.getUsers();
}
}
接口适配器层 - interfaces/views/UserList.vue
<template>
<div>
<h1>User List</h1>
<ul>
<li v-for="user in users" :key="user.id">
{{ user.name }} - {{ user.email }}
</li>
</ul>
</div>
</template>
<script>
import { UserUseCase } from '../../core/use-cases/user.usecase';
export default {
data() {
return {
users: []
};
},
async created() {
const userUseCase = new UserUseCase(this.$api);
this.users = await userUseCase.getUsers();
}
};
</script>
干净架构的优势
- 长期可维护性:清晰的层次结构使代码更易于理解和修改
- 框架独立性:可以轻松更换Express或Vue而不影响核心业务逻辑
- 可测试性:各层可以独立测试,特别是核心业务逻辑
- 团队协作:不同团队可以并行工作在不同层次上
- 适应变化:业务规则变更不会影响UI或数据库层
实际开发场景示例
场景:用户注册功能
后端流程:
- 实体层:定义User实体和验证规则
- 用例层:CreateUserUseCase处理注册逻辑(检查邮箱唯一性等)
- 接口适配器层:UserController处理HTTP请求,调用用例
- 框架层:Express路由将请求路由到控制器
前端流程:
- 实体层:定义与后端一致的User模型
- 用例层:UserUseCase封装API调用和前端业务逻辑
- 接口适配器层:注册组件处理表单提交,调用用例
- 框架层:Vue处理UI渲染和用户交互
👋最后
在Express和Vue的全栈开发中应用干净架构,虽然初期需要更多的设计和规划,但长期来看会带来显著的好处。通过清晰的关注点分离,你的应用将变得更加灵活、可维护和可测试。特别是在业务逻辑复杂的应用中,干净架构能够帮助你更好地管理复杂性,适应不断变化的需求。
记住,架构不是一成不变的,应该根据项目需求和团队情况适当调整干净架构的原则。
最终目标是创建可维护、可扩展且高质量的软件,而不是刻板地遵循某种架构模式!!