干净架构(Clean Architecture)在Express和Vue全栈开发中的应用

536 阅读5分钟

🤔什么是干净架构?

干净架构(Clean Architecture)是由Robert C. Martin(又称Uncle Bob)提出的一种软件设计理念。

它强调将业务逻辑框架UI基础设施分离,使系统更易于维护、测试和适应变化。

在Express(后端)和Vue(前端)的全栈开发中应用【干净架构】,可以使你的应用更加健壮【所谓健壮性:比如用户乱输入、网络抽风、数据异常时,程序能妥善处理而不是直接死掉】、可测试和可维护。

干净架构的核心原则

  1. 独立于框架:不依赖任何特定框架的特性,所以泛用性会比较高
  2. 可测试:业务逻辑可以在没有UI、数据库或外部服务的情况下测试
  3. 独立于UI:UI可以轻松更改而不影响业务逻辑
  4. 独立于数据库:可以轻松切换数据库实现
  5. 独立于任何外部服务:业务逻辑不知道外部服务的实现细节

🧩干净架构的层次结构【重要】

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/          # 共享代码

下面讲一下上面的这个项目结构是如何运作的

调用流程示例(用户注册)

  1. HTTP请求到达routes/user.ts → 转发给controllers/UserController.create()
  2. 控制器解析请求体 → 调用domain/use-cases/CreateUserUseCase
  3. 用例验证业务规则 → 通过interfaces/repositories/UserRepository(接口)保存用户; 【注:use-cases(用例) 存放具体业务逻辑(如用户注册、订单创建),负责协调数据流(调用实体验证规则→通过仓储接口保存数据),是连接实体层与外部适配器的核心业务操作单元。】
  4. 仓库实现(如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>

干净架构的优势

  1. 长期可维护性:清晰的层次结构使代码更易于理解和修改
  2. 框架独立性:可以轻松更换Express或Vue而不影响核心业务逻辑
  3. 可测试性:各层可以独立测试,特别是核心业务逻辑
  4. 团队协作:不同团队可以并行工作在不同层次上
  5. 适应变化:业务规则变更不会影响UI或数据库层

实际开发场景示例

场景:用户注册功能

后端流程

  1. 实体层:定义User实体和验证规则
  2. 用例层:CreateUserUseCase处理注册逻辑(检查邮箱唯一性等)
  3. 接口适配器层:UserController处理HTTP请求,调用用例
  4. 框架层:Express路由将请求路由到控制器

前端流程

  1. 实体层:定义与后端一致的User模型
  2. 用例层:UserUseCase封装API调用和前端业务逻辑
  3. 接口适配器层:注册组件处理表单提交,调用用例
  4. 框架层:Vue处理UI渲染和用户交互

👋最后

在Express和Vue的全栈开发中应用干净架构,虽然初期需要更多的设计和规划,但长期来看会带来显著的好处。通过清晰的关注点分离,你的应用将变得更加灵活、可维护和可测试。特别是在业务逻辑复杂的应用中,干净架构能够帮助你更好地管理复杂性,适应不断变化的需求。

记住,架构不是一成不变的,应该根据项目需求和团队情况适当调整干净架构的原则。

最终目标是创建可维护、可扩展且高质量的软件,而不是刻板地遵循某种架构模式!!