模块化编程实践指南
作为模块化编程系列的总览文章,本文系统梳理了从基础概念到大型项目架构设计的全栈知识体系。首先介绍分层架构设计,包括前端 React 分层与后端 NestJS 分层,以及 DDD 四层架构。随后探讨微前端架构,解析 iframe、Web Components、single-spa、qiankun、模块联邦等方案的优劣势,并提供模块联邦的完整实战配置。系列文章涵盖 CommonJS、AMD、UMD、ES Modules 等规范演进,以及 TypeScript 模块增强、工程化实践与 Node.js 模块化。
1. 引言
模块化编程是现代 JavaScript/TypeScript 开发的核心原则,它通过将复杂系统拆分为独立可复用的模块,降低系统复杂性并提高可维护性与可复用性。因此,我们通过系列文章详细介绍了模块化的基础概念、演进历程、不同规范的深度解析、TypeScript模块化增强、ES Modules 核心语法与实践、模块化编程原则与最佳实践、前端工程化中的模块化、Node.js中的模块化实践、大型项目中的模块化设计、模块化与代码维护以及未来发展趋势。
前面的系列文章主要有:
- 模块化编程编程初体验与基础介绍
- JavaScript 模块化规范全景图:从 CJS 到 ESM
- 回归标准,拥抱原生:ES Modules 核心规范与实践解析
- 从逻辑隔离到类型契约:TypeScript 对模块化编程的增强
- 模块化编程原则梳理与总结
- Node.js 模块化编程梳理与总结
本文将重点介绍大型项目中的模块化设计。
2.分层架构设计
分层架构是大型项目中常用的模块化设计模式,它将系统分为多个层次,每层负责特定的功能。
2.1 前端分层架构
以 React 技术栈为例,一个典型的前端分层架构通常包含以下核心层次:
| 层级 | 说明 |
|---|---|
| 表现层 | 负责 UI 渲染,包含组件与页面,遵循原子设计(Atomic Design)方法论 |
| 业务逻辑层 | 封装业务规则与流程,通过自定义 Hooks 和服务实现 |
| 数据访问层 | 处理与后端的通信,包括 API 请求、数据缓存与同步策略 |
| 状态管理层 | 管理全局与局部状态,确保数据流的可预测性 |
| 路由层 | 定义导航结构与路由守卫,处理权限控制与代码分割 |
| 基础设施层 | 提供横切关注点支持,包括工具函数、常量定义与配置管理 |
以下是一个基于 React + TypeScript 的现代项目目录结构示例:
src/
├── app/ # 应用层:应用配置和入口
│ ├── App.tsx # 应用根组件
│ ├── Router.tsx # 路由配置
│ └── providers/ # 全局 Provider
│ ├── QueryProvider.tsx # React Query Provider
│ ├── ReduxProvider.tsx # Redux Provider
│ └── ThemeProvider.tsx # 主题 Provider
│
├── pages/ # 页面层:页面组件(路由级别)
│ ├── Home/
│ │ ├── index.tsx # 页面组件
│ │ ├── HomePage.css # 页面样式
│ │ └── HomePage.test.tsx # 页面测试
│ ├── User/
│ │ ├── index.tsx
│ │ ├── components/ # 页面专属组件
│ │ └── hooks/ # 页面专属 hooks
│ └── Dashboard/
│ └── index.tsx
│
├── features/ # 特性层:业务功能模块
│ ├── auth/ # 认证功能
│ │ ├── components/ # 认证相关组件
│ │ │ ├── LoginForm.tsx
│ │ │ └── RegisterForm.tsx
│ │ ├── hooks/ # 认证相关 hooks
│ │ │ ├── useAuth.ts
│ │ │ └── useLogin.ts
│ │ ├── api/ # 认证相关 API
│ │ │ └── authApi.ts
│ │ ├── store/ # 认证相关状态
│ │ │ └── authSlice.ts
│ │ └── types/ # 认证相关类型
│ │ └── auth.types.ts
│ ├── user/ # 用户管理功能
│ │ ├── components/
│ │ ├── hooks/
│ │ └── api/
│ └── product/ # 产品管理功能
│ ├── components/
│ └── hooks/
│
├── components/ # 组件层:可复用 UI 组件(原子设计)
│ ├── atoms/ # 原子组件:最小 UI 单元
│ │ ├── Button/
│ │ │ ├── Button.tsx
│ │ │ ├── Button.styles.ts
│ │ │ ├── Button.types.ts
│ │ │ └── Button.test.tsx
│ │ ├── Input/
│ │ ├── Icon/
│ │ └── Typography/
│ ├── molecules/ # 分子组件:原子组件组合
│ │ ├── FormField/
│ │ ├── SearchBar/
│ │ └── Card/
│ ├── organisms/ # 有机体组件:复杂 UI 区块
│ │ ├── Header/
│ │ ├── Sidebar/
│ │ ├── DataTable/
│ │ └── Modal/
│ └── templates/ # 模板组件:页面布局模板
│ ├── MainLayout/
│ ├── AuthLayout/
│ └── DashboardLayout/
│
├── hooks/ # Hooks 层:全局自定义 Hooks
│ ├── useDebounce.ts
│ ├── useLocalStorage.ts
│ ├── useMediaQuery.ts
│ └── useApi/
│ ├── useApi.ts
│ └── useApi.types.ts
│
├── api/ # API 层:数据请求和缓存
│ ├── client/ # API 客户端配置
│ │ ├── axiosClient.ts # Axios 实例配置
│ │ ├── interceptors.ts # 请求/响应拦截器
│ │ └── errorHandler.ts # 错误处理
│ ├── services/ # API 服务
│ │ ├── userService.ts
│ │ ├── productService.ts
│ │ └── authService.ts
│ ├── endpoints/ # API 端点定义
│ │ └── endpoints.ts
│ └── queries/ # React Query 查询
│ ├── userQueries.ts
│ └── productQueries.ts
│
├── store/ # 状态管理层:全局状态管理
│ ├── index.ts # Store 配置
│ ├── slices/ # Redux Slices
│ │ ├── userSlice.ts
│ │ ├── uiSlice.ts
│ │ └── cartSlice.ts
│ ├── middleware/ # Redux 中间件
│ │ └── logger.ts
│ └── selectors/ # Redux 选择器
│ └── userSelectors.ts
│
├── types/ # 类型层:TypeScript 类型定义
│ ├── common.types.ts # 通用类型
│ ├── api.types.ts # API 响应类型
│ ├── models.types.ts # 数据模型类型
│ └── env.d.ts # 环境变量类型
│
├── utils/ # 工具层:工具函数
│ ├── formatting/ # 格式化工具
│ │ ├── date.ts
│ │ ├── number.ts
│ │ └── string.ts
│ ├── validation/ # 验证工具
│ │ ├── form.ts
│ │ └── schema.ts
│ ├── storage/ # 存储工具
│ │ ├── localStorage.ts
│ │ └── sessionStorage.ts
│ └── helpers/ # 辅助函数
│ ├── array.ts
│ └── object.ts
│
├── constants/ # 常量层:应用常量
│ ├── routes.ts # 路由常量
│ ├── api.ts # API 常量
│ └── config.ts # 配置常量
│
├── styles/ # 样式层:全局样式
│ ├── theme/ # 主题配置
│ │ ├── colors.ts
│ │ ├── typography.ts
│ │ └── spacing.ts
│ ├── global.ts # 全局样式
│ └── variables.ts # CSS 变量
│
├── assets/ # 静态资源层
│ ├── images/
│ ├── icons/
│ └── fonts/
│
├── config/ # 配置层:应用配置
│ ├── app.config.ts
│ └── env.config.ts
│
├── router/ # 路由层:路由配置
│ ├── index.tsx
│ ├── routes.tsx # 路由定义
│ └── guards/ # 路由守卫
│ └── authGuard.ts
│
├── test/ # 测试层:测试配置和工具
│ ├── setup.ts
│ ├── mocks/ # Mock 数据
│ └── utils/ # 测试工具
│
├── main.tsx # 应用入口
└── vite-env.d.ts # Vite 环境类型2.2 后端模块化分层架构
以 NestJS 技术栈为例,典型的后端模块化分层架构包含以下核心组件:
| 模块 | 说明 |
|---|---|
| 控制器(Controller) | 负责处理 HTTP 请求与响应,定义 API 路由端点。 |
| 服务层(Service) | 封装核心业务逻辑,协调复杂业务流程。 |
| 数据访问层(Repository) | 负责数据库交互,抽象并封装数据持久化操作。 |
| 模型层(Entity/DTO) | 定义数据实体结构与数据传输对象。 |
| 守卫(Guard) | 实现认证授权与权限控制。 |
| 拦截器(Interceptor) | 处理请求/响应转换及日志记录。 |
| 过滤器(Filter) | 统一异常处理与错误响应格式化。 |
| 管道(Pipe) | 执行数据验证与类型转换。 |
以下是一个基于 NestJS + TypeScript 的现代项目目录结构示例:
src/
├── modules/ # 业务模块目录
│ ├── user/ # 用户模块
│ │ ├── controllers/ # 控制器:处理 HTTP 请求
│ │ │ └── user.controller.ts
│ │ ├── services/ # 服务层:业务逻辑
│ │ │ └── user.service.ts
│ │ ├── repositories/ # 数据访问层:数据库操作
│ │ │ └── user.repository.ts
│ │ ├── entities/ # 实体层:数据库模型
│ │ │ └── user.entity.ts
│ │ ├── dto/ # DTO:数据传输对象
│ │ │ ├── create-user.dto.ts
│ │ │ └── update-user.dto.ts
│ │ ├── guards/ # 守卫:权限控制
│ │ │ └── user.guard.ts
│ │ ├── interceptors/ # 拦截器:请求/响应处理
│ │ │ └── user.interceptor.ts
│ │ └── user.module.ts # 模块定义
│ ├── auth/ # 认证模块
│ ├── product/ # 产品模块
│ └── order/ # 订单模块
├── common/ # 公共模块
│ ├── filters/ # 全局异常过滤器
│ │ └── http-exception.filter.ts
│ ├── guards/ # 全局守卫
│ │ └── auth.guard.ts
│ ├── interceptors/ # 全局拦截器
│ │ ├── logging.interceptor.ts
│ │ └── transform.interceptor.ts
│ ├── pipes/ # 全局管道
│ │ └── validation.pipe.ts
│ ├── decorators/ # 自定义装饰器
│ │ └── roles.decorator.ts
│ └── middleware/ # 中间件
│ └── logger.middleware.ts
├── config/ # 配置文件
│ ├── database.config.ts
│ ├── app.config.ts
│ └── swagger.config.ts
├── database/ # 数据库配置
│ ├── migrations/ # 数据库迁移
│ ├── seeds/ # 数据填充
│ └── database.module.ts
├── utils/ # 工具函数
│ ├── date.util.ts
│ └── string.util.ts
├── types/ # TypeScript 类型定义
│ └── express.d.ts
├── app.module.ts # 应用主模块
└── main.ts # 应用入口2.3 领域驱动设计(DDD)与模块化
领域驱动设计(Domain-Driven Design,简称 DDD)是一种以业务领域为核心的软件设计方法论。它强调通过深入理解业务领域,建立丰富的领域模型,并将业务逻辑与技术实现紧密结合,从而构建出能够准确反映业务需求、易于维护和扩展的软件系统。
2.3.1 DDD 的核心理念
DDD 的核心思想是让软件设计回归业务本质,通过以下关键理念指导开发:
- 以领域为中心:软件的核心是业务领域,技术实现应服务于业务需求
- 统一语言(Ubiquitous Language):开发团队与业务专家使用相同的术语,确保沟通准确
- 分层架构:将系统分为用户界面层、应用层、领域层、基础设施层,职责清晰
- 限界上下文(Bounded Context):明确领域的边界,避免模型混淆
- 持续重构:通过不断重构深化对领域的理解
2.3.2 DDD 的关键概念
DDD 定义了一系列关键概念,帮助开发者构建清晰的领域模型:
| 概念 | 定义 | 示例 |
|---|---|---|
| 领域 | 业务相关的概念和规则,是软件要解决的核心问题域 | 电商系统中的订单、用户、商品 |
| 实体 | 具有唯一标识的对象,标识在整个生命周期中保持不变 | 用户(通过 userId 标识)、订单(通过 orderId 标识) |
| 值对象 | 没有唯一标识的对象,通过属性值定义,不可变 | 地址(省、市、区、详细地址)、金额(数值+货币) |
| 聚合 | 一组相关对象的集合,作为数据修改的单元 | 订单聚合(包含订单项、收货地址、支付信息) |
| 聚合根 | 聚合的根实体,外部只能通过聚合根访问聚合内部对象 | 订单聚合中的 Order 实体 |
| 领域服务 | 不属于任何实体或值对象的领域逻辑,通常涉及多个领域对象 | 订单计算服务(计算订单总价、折扣) |
| 仓储 | 封装数据访问逻辑,提供类似集合的接口来访问领域对象 | UserRepository、OrderRepository |
| 工厂 | 负责创建复杂对象或聚合,封装创建逻辑 | OrderFactory(创建订单及其订单项) |
| 应用服务 | 协调领域对象完成业务用例,不包含业务规则 | OrderApplicationService(处理下单流程) |
2.3.3 DDD 的分层架构
DDD 推荐采用四层架构,每层职责明确:
┌─────────────────────────────────────────┐
│ 用户界面层 │ ← 表现层:处理用户交互,展示数据
├─────────────────────────────────────────┤
│ 应用层 │ ← 编排层:协调领域对象,处理用例
├─────────────────────────────────────────┤
│ 领域层 │ ← 核心层:业务逻辑,领域模型
├─────────────────────────────────────────┤
│ 基础设施层 │ ← 支撑层:技术实现,外部服务
└─────────────────────────────────────────┘各层职责详解: 各层职责详解:
| 层级 | 职责说明 |
|---|---|
| 用户界面层 | • 处理 HTTP 请求/响应 • 数据验证和格式转换 • 渲染视图或返回 JSON |
| 应用层 | • 协调领域对象完成业务用例 • 管理事务边界 • 权限控制 |
| 领域层 | • 包含业务核心逻辑 • 定义实体、值对象、领域服务 • 表达业务规则 |
| 基础设施层 | • 实现仓储接口 • 数据库访问 • 外部服务调用 • 日志、缓存等技术实现 |
2.3.4 全栈 DDD 项目目录结构示例
以下是一个基于 React(前端)+ Node.js/NestJS(后端)的全栈 DDD 项目目录结构:
project-root/
├── frontend/ # 前端项目(React + TypeScript)
│ ├── src/
│ │ ├── app/ # 应用层
│ │ │ ├── App.tsx
│ │ │ └── providers/
│ │ ├── modules/ # 业务模块(按限界上下文组织)
│ │ │ ├── order/ # 订单上下文
│ │ │ │ ├── domain/ # 领域模型(前端简化版)
│ │ │ │ │ ├── entities/
│ │ │ │ │ │ └── Order.ts
│ │ │ │ │ ├── value-objects/
│ │ │ │ │ │ └── OrderStatus.ts
│ │ │ │ │ └── services/
│ │ │ │ │ └── OrderCalculator.ts
│ │ │ │ ├── application/ # 应用服务
│ │ │ │ │ ├── services/
│ │ │ │ │ │ └── OrderApplicationService.ts
│ │ │ │ │ └── hooks/
│ │ │ │ │ └── useOrder.ts
│ │ │ │ ├── infrastructure/ # 基础设施
│ │ │ │ │ ├── api/
│ │ │ │ │ │ └── OrderApi.ts
│ │ │ │ │ └── repositories/
│ │ │ │ │ └── OrderRepository.ts
│ │ │ │ └── ui/ # 用户界面
│ │ │ │ ├── components/
│ │ │ │ │ ├── OrderList.tsx
│ │ │ │ │ └── OrderDetail.tsx
│ │ │ │ └── pages/
│ │ │ │ └── OrderPage.tsx
│ │ │ ├── user/ # 用户上下文
│ │ │ │ ├── domain/
│ │ │ │ ├── application/
│ │ │ │ ├── infrastructure/
│ │ │ │ └── ui/
│ │ │ └── product/ # 产品上下文
│ │ │ ├── domain/
│ │ │ ├── application/
│ │ │ ├── infrastructure/
│ │ │ └── ui/
│ │ ├── shared/ # 共享模块
│ │ │ ├── domain/ # 共享领域概念
│ │ │ │ ├── value-objects/
│ │ │ │ │ ├── Money.ts
│ │ │ │ │ └── Address.ts
│ │ │ │ └── types/
│ │ │ │ └── CommonTypes.ts
│ │ │ ├── infrastructure/ # 共享基础设施
│ │ │ │ ├── api/
│ │ │ │ │ └── BaseApi.ts
│ │ │ │ └── storage/
│ │ │ │ └── LocalStorage.ts
│ │ │ └── utils/ # 工具函数
│ │ │ ├── formatting.ts
│ │ │ └── validation.ts
│ │ └── main.tsx
│ └── package.json
│
├── backend/ # 后端项目(Node.js + NestJS)
│ ├── src/
│ │ ├── modules/ # 业务模块(按限界上下文组织)
│ │ │ ├── order/ # 订单上下文
│ │ │ │ ├── domain/ # 领域层
│ │ │ │ │ ├── entities/
│ │ │ │ │ │ ├── order.entity.ts
│ │ │ │ │ │ └── order-item.entity.ts
│ │ │ │ │ ├── value-objects/
│ │ │ │ │ │ ├── order-status.vo.ts
│ │ │ │ │ │ ├── money.vo.ts
│ │ │ │ │ │ └── address.vo.ts
│ │ │ │ │ ├── aggregates/
│ │ │ │ │ │ └── order.aggregate.ts
│ │ │ │ │ ├── services/
│ │ │ │ │ │ ├── order-calculation.service.ts
│ │ │ │ │ │ └── order-validation.service.ts
│ │ │ │ │ ├── events/
│ │ │ │ │ │ ├── order-created.event.ts
│ │ │ │ │ │ └── order-paid.event.ts
│ │ │ │ │ └── repositories/
│ │ │ │ │ └── order.repository.interface.ts
│ │ │ │ ├── application/ # 应用层
│ │ │ │ │ ├── services/
│ │ │ │ │ │ ├── order.service.ts
│ │ │ │ │ │ └── order-query.service.ts
│ │ │ │ │ ├── commands/
│ │ │ │ │ │ ├── create-order.command.ts
│ │ │ │ │ │ └── cancel-order.command.ts
│ │ │ │ │ ├── handlers/
│ │ │ │ │ │ ├── create-order.handler.ts
│ │ │ │ │ │ └── order-event.handler.ts
│ │ │ │ │ └── dtos/
│ │ │ │ │ ├── create-order.dto.ts
│ │ │ │ │ └── order-response.dto.ts
│ │ │ │ ├── infrastructure/ # 基础设施层
│ │ │ │ │ ├── persistence/
│ │ │ │ │ │ ├── typeorm/
│ │ │ │ │ │ │ ├── order.orm-entity.ts
│ │ │ │ │ │ │ └── order.repository.impl.ts
│ │ │ │ │ │ └── mappers/
│ │ │ │ │ │ └── order.mapper.ts
│ │ │ │ │ ├── adapters/
│ │ │ │ │ │ ├── payment-gateway.adapter.ts
│ │ │ │ │ │ └── notification.adapter.ts
│ │ │ │ │ └── messaging/
│ │ │ │ │ └── order-event.publisher.ts
│ │ │ │ ├── interfaces/ # 用户界面层
│ │ │ │ │ ├── controllers/
│ │ │ │ │ │ ├── order.controller.ts
│ │ │ │ │ │ └── order-query.controller.ts
│ │ │ │ │ └── resolvers/
│ │ │ │ │ └── order.resolver.ts
│ │ │ │ └── order.module.ts
│ │ │ ├── user/ # 用户上下文
│ │ │ │ ├── domain/
│ │ │ │ ├── application/
│ │ │ │ ├── infrastructure/
│ │ │ │ ├── interfaces/
│ │ │ │ └── user.module.ts
│ │ │ └── product/ # 产品上下文
│ │ │ ├── domain/
│ │ │ ├── application/
│ │ │ ├── infrastructure/
│ │ │ ├── interfaces/
│ │ │ └── product.module.ts
│ │ ├── shared/ # 共享模块
│ │ │ ├── domain/ # 共享领域概念
│ │ │ │ ├── value-objects/
│ │ │ │ │ ├── email.vo.ts
│ │ │ │ │ └── phone.vo.ts
│ │ │ │ └── events/
│ │ │ │ └── domain-event.base.ts
│ │ │ ├── infrastructure/ # 共享基础设施
│ │ │ │ ├── database/
│ │ │ │ │ └── database.module.ts
│ │ │ │ ├── messaging/
│ │ │ │ │ └── event-bus.ts
│ │ │ │ └── logging/
│ │ │ │ └── logger.service.ts
│ │ │ └── utils/ # 工具函数
│ │ │ ├── date.util.ts
│ │ │ └── string.util.ts
│ │ ├── app.module.ts
│ │ └── main.ts
│ └── package.json
│
└── shared-types/ # 前后端共享类型定义
├── order/
│ ├── order.types.ts
│ └── order.dto.ts
├── user/
│ └── user.types.ts
└── common/
├── api-response.types.ts
└── pagination.types.ts2.3.5 DDD 实践指导
在实际项目中应用 DDD,需要遵循以下实践原则:
1、识别限界上下文,这是 DDD 中最重要的概念之一,它定义了模型的边界:
// 示例:订单上下文中的 Order 实体
// backend/src/modules/order/domain/entities/order.entity.ts
export class Order {
private constructor(
private readonly id: OrderId,
private customerId: CustomerId,
private orderItems: OrderItem[],
private status: OrderStatus,
private shippingAddress: Address,
private createdAt: Date,
) {}
// 业务方法:添加订单项
addItem(product: Product, quantity: number): void {
if (this.status !== OrderStatus.PENDING) {
throw new OrderCannotBeModifiedError(this.id);
}
const existingItem = this.orderItems.find(
item => item.productId.equals(product.id)
);
if (existingItem) {
existingItem.increaseQuantity(quantity);
} else {
this.orderItems.push(OrderItem.create(product, quantity));
}
}
// 业务方法:计算总价
calculateTotal(): Money {
return this.orderItems.reduce(
(total, item) => total.add(item.calculateSubtotal()),
Money.zero()
);
}
// 业务方法:确认订单
confirm(): void {
if (this.status !== OrderStatus.PENDING) {
throw new OrderCannotBeConfirmedError(this.id);
}
this.status = OrderStatus.CONFIRMED;
}
}2. 使用值对象封装业务规则
值对象是不可变的,能够有效封装业务规则:
// 示例:金额值对象
// backend/src/modules/order/domain/value-objects/money.vo.ts
export class Money {
private constructor(
private readonly amount: number,
private readonly currency: string,
) {
if (amount < 0) {
throw new InvalidMoneyError('Amount cannot be negative');
}
if (!currency || currency.length !== 3) {
throw new InvalidMoneyError('Currency must be a 3-letter code');
}
}
static from(amount: number, currency: string = 'CNY'): Money {
return new Money(Math.round(amount * 100) / 100, currency);
}
add(other: Money): Money {
if (this.currency !== other.currency) {
throw new CurrencyMismatchError();
}
return Money.from(this.amount + other.amount, this.currency);
}
multiply(factor: number): Money {
return Money.from(this.amount * factor, this.currency);
}
getAmount(): number {
return this.amount;
}
getCurrency(): string {
return this.currency;
}
}3. 定义清晰的仓储接口
仓储接口应定义在领域层,实现在基础设施层:
// 领域层:仓储接口
// backend/src/modules/order/domain/repositories/order.repository.interface.ts
export interface OrderRepository {
findById(id: OrderId): Promise<Order | null>;
findByCustomerId(customerId: CustomerId): Promise<Order[]>;
save(order: Order): Promise<void>;
delete(order: Order): Promise<void>;
}
// 基础设施层:仓储实现
// backend/src/modules/order/infrastructure/persistence/typeorm/order.repository.impl.ts
@Injectable()
export class OrderRepositoryImpl implements OrderRepository {
constructor(
@InjectRepository(OrderOrmEntity)
private readonly ormRepository: Repository<OrderOrmEntity>,
) {}
async findById(id: OrderId): Promise<Order | null> {
const ormEntity = await this.ormRepository.findOne({
where: { id: id.getValue() },
relations: ['items'],
});
return ormEntity ? OrderMapper.toDomain(ormEntity) : null;
}
async save(order: Order): Promise<void> {
const ormEntity = OrderMapper.toOrm(order);
await this.ormRepository.save(ormEntity);
}
}4. 使用领域事件解耦
领域事件用于表达领域内发生的事情,实现模块间解耦:
// 领域事件定义
// backend/src/modules/order/domain/events/order-created.event.ts
export class OrderCreatedEvent implements DomainEvent {
constructor(
public readonly orderId: OrderId,
public readonly customerId: CustomerId,
public readonly totalAmount: Money,
public readonly occurredAt: Date,
) {}
}
// 事件处理器
// backend/src/modules/order/application/handlers/order-event.handler.ts
@EventsHandler(OrderCreatedEvent)
export class OrderCreatedEventHandler implements IEventHandler<OrderCreatedEvent> {
constructor(
private readonly notificationService: NotificationService,
private readonly inventoryService: InventoryService,
) {}
async handle(event: OrderCreatedEvent): Promise<void> {
// 发送通知
await this.notificationService.sendOrderConfirmation(
event.customerId,
event.orderId,
);
// 预留库存
await this.inventoryService.reserveStock(event.orderId);
}
}5. 前端领域模型设计
前端也需要领域模型,但可以适当简化:
// 前端领域模型
// frontend/src/modules/order/domain/entities/Order.ts
export class Order {
constructor(
public readonly id: string,
public customerId: string,
public items: OrderItem[],
public status: OrderStatus,
public totalAmount: number,
) {}
// 前端业务逻辑:是否可以取消
canCancel(): boolean {
return this.status === OrderStatus.PENDING ||
this.status === OrderStatus.CONFIRMED;
}
// 前端业务逻辑:计算订单项数量
getTotalItemCount(): number {
return this.items.reduce((sum, item) => sum + item.quantity, 0);
}
// 前端业务逻辑:格式化显示
getFormattedTotal(): string {
return `¥${this.totalAmount.toFixed(2)}`;
}
}
// 前端应用服务
// frontend/src/modules/order/application/services/OrderApplicationService.ts
export class OrderApplicationService {
constructor(private orderApi: OrderApi) {}
async createOrder(items: CartItem[]): Promise<Order> {
// 调用后端 API
const orderDto = await this.orderApi.createOrder({
items: items.map(item => ({
productId: item.productId,
quantity: item.quantity,
})),
});
// 转换为前端领域模型
return new Order(
orderDto.id,
orderDto.customerId,
orderDto.items,
orderDto.status,
orderDto.totalAmount,
);
}
}2.3.6 DDD 实施建议
在项目中实施 DDD 时,建议遵循以下步骤:
| 阶段 | 核心任务 |
|---|---|
| 第一阶段:领域建模 | • 与业务专家深入沟通,理解业务领域 • 识别核心业务概念,建立统一语言 • 绘制领域模型图,明确实体、值对象、聚合 • 定义限界上下文,确定上下文映射关系 |
| 第二阶段:架构设计 | • 根据限界上下文划分模块 • 设计分层架构,明确各层职责 • 定义领域服务、应用服务、仓储接口 • 设计领域事件机制 |
| 第三阶段:编码实现 | • 实现领域层:实体、值对象、领域服务 • 实现应用层:应用服务、命令/查询处理器 • 实现基础设施层:仓储实现、外部服务适配器 • 实现用户界面层:控制器、DTO 转换 |
| 第四阶段:持续演进 | • 通过重构深化领域模型 • 根据业务变化调整限界上下文 • 优化聚合边界和领域服务 • 完善领域事件体系 |
2.3.7 DDD 的优势与挑战
| 优势 | 挑战 |
|---|---|
| • 业务导向:软件设计紧密围绕业务需求,提高业务价值 • 易于维护:清晰的分层和模块划分,降低维护成本 • 高内聚低耦合:聚合和限界上下文确保模块独立性 • 可扩展性强:新增业务功能时,易于定位和扩展 • 沟通效率高:统一语言减少开发与业务之间的误解 | • 学习曲线陡峭:DDD 概念较多,需要时间学习和实践 • 初期成本高:前期需要投入大量时间进行领域建模 • 过度设计风险:小型项目可能不需要完整的 DDD 架构 • 团队要求高:需要团队成员对业务有深入理解 • 技术复杂度:需要处理聚合事务、事件一致性等技术问题 |
2.4 微前端与模块化
微前端是一种将前端应用拆分为多个独立、可自治的子应用的架构模式,每个子应用可以独立开发、测试、部署,甚至可以使用不同的技术栈。这种架构模式特别适合大型团队协作和复杂业务场景。
2.4.1 微前端的核心理念
微前端借鉴了微服务的思想,将前端应用按照业务领域或功能模块进行拆分,实现以下目标:
- 技术栈无关:不同子应用可以使用不同的框架(React、Vue、Angular 等)
- 独立开发部署:每个子应用可以独立开发、测试和部署,互不影响
- 团队自治:不同团队可以独立负责不同的子应用,提高开发效率
- 渐进式升级:可以逐步升级或替换旧的子应用,降低技术债务
2.4.2 微前端的模块化设计原则
在微前端架构中,模块化设计需要遵循以下原则:
| 设计原则 | 说明 | 实践建议 |
|---|---|---|
| 独立模块 | 每个子应用都是一个独立的模块,拥有独立的代码仓库和构建流程 | 使用 Monorepo 或 Multirepo 管理子应用 |
| 通信机制 | 子应用之间通过事件总线、共享状态或自定义协议进行通信 | 避免直接依赖,使用发布-订阅模式 |
| 代码共享 | 通过模块联邦或 npm 包实现公共代码和组件的共享 | 共享基础库、UI 组件库、工具函数 |
| 样式隔离 | 每个子应用的样式应该隔离,避免样式冲突 | 使用 CSS Modules、CSS-in-JS 或 Shadow DOM |
| 路由管理 | 主应用负责路由分发,子应用管理内部路由 | 使用 single-spa 或 qiankun 等框架 |
2.4.3 微前端实现方案对比
目前主流的微前端实现方案有以下几种:
| 方案 | 技术实现 | 优势 | 劣势 | 适用场景 |
|---|---|---|---|---|
| iframe | 使用 iframe 加载子应用 | 天然隔离,实现简单 | 性能差,通信复杂,SEO 不友好 | 简单集成第三方系统 |
| Web Components | 使用自定义元素封装子应用 | 标准化,浏览器原生支持 | 浏览器兼容性,生态不成熟 | 对标准化要求高的项目 |
| single-spa | 基于路由的微前端框架 | 成熟稳定,生态丰富 | 配置复杂,学习成本高 | 大型企业级应用 |
| qiankun | 基于 single-spa 的封装 | 开箱即用,文档完善 | 依赖 single-spa,定制性受限 | 快速搭建微前端应用 |
| Module Federation | Webpack 5 模块联邦 | 原生支持,性能优秀 | 需要 Webpack 5,调试复杂 | 现代化前端项目 |
2.4.4 模块联邦(Module Federation)实战
模块联邦是 Webpack 5 引入的新特性,它允许多个 Webpack 构建之间共享代码,是微前端架构的理想选择。
场景示例:假设我们有一个电商平台,包含主应用(mainApp)、商品子应用(productApp)和用户子应用(userApp)。
1. 主应用配置(mainApp)
主应用负责加载子应用,并提供公共依赖:
// 主应用 webpack.config.js
const ModuleFederationPlugin = require('webpack/lib/container/ModuleFederationPlugin');
module.exports = {
// ...
plugins: [
new ModuleFederationPlugin({
name: 'mainApp', // 主应用名称
remotes: { // 远程子应用配置
productApp: 'productApp@http://localhost:3001/remoteEntry.js',
userApp: 'userApp@http://localhost:3002/remoteEntry.js'
},
shared: { // 共享依赖
react: {
singleton: true, // 单例模式,确保只有一个 React 实例
requiredVersion: '^18.0.0',
eager: false // 懒加载
},
'react-dom': {
singleton: true,
requiredVersion: '^18.0.0'
},
lodash: {
singleton: true,
requiredVersion: '^4.17.0'
}
}
})
]
};2. 商品子应用配置(productApp)
商品子应用暴露商品列表和商品详情组件:
// 子应用 productApp webpack.config.js
module.exports = {
// ...
plugins: [
new ModuleFederationPlugin({
name: 'productApp',
filename: 'remoteEntry.js', // 远程入口文件
exposes: { // 暴露的模块
'./ProductList': './src/components/ProductList',
'./ProductDetail': './src/components/ProductDetail',
'./productService': './src/services/productService'
},
shared: {
react: {
singleton: true,
requiredVersion: '^18.0.0'
},
'react-dom': {
singleton: true,
requiredVersion: '^18.0.0'
}
}
})
]
};3. 用户子应用配置(userApp)
用户子应用暴露用户信息和登录组件:
// 子应用 userApp webpack.config.js
module.exports = {
// ...
plugins: [
new ModuleFederationPlugin({
name: 'userApp',
filename: 'remoteEntry.js',
exposes: {
'./UserInfo': './src/components/UserInfo',
'./Login': './src/components/Login',
'./userService': './src/services/userService'
},
shared: {
react: {
singleton: true,
requiredVersion: '^18.0.0'
},
'react-dom': {
singleton: true,
requiredVersion: '^18.0.0'
}
}
})
]
};4. 主应用中使用子应用组件
// 主应用 App.tsx
import React, { Suspense, lazy } from 'react';
// 动态导入子应用组件
const ProductList = lazy(() => import('productApp/ProductList'));
const ProductDetail = lazy(() => import('productApp/ProductDetail'));
const UserInfo = lazy(() => import('userApp/UserInfo'));
const Login = lazy(() => import('userApp/Login'));
// 导入子应用服务
import { productService } from 'productApp/productService';
import { userService } from 'userApp/userService';
function App() {
const handleProductClick = async (productId: string) => {
// 调用商品子应用的服务
const product = await productService.getProductById(productId);
console.log('Product details:', product);
};
return (
<div className="app">
<header>
<Suspense fallback={<div>Loading...</div>}>
<UserInfo />
</Suspense>
</header>
<main>
<Suspense fallback={<div>Loading Product List...</div>}>
<ProductList onProductClick={handleProductClick} />
</Suspense>
<Suspense fallback={<div>Loading Product Detail...</div>}>
<ProductDetail productId="123" />
</Suspense>
</main>
<footer>
<Suspense fallback={<div>Loading...</div>}>
<Login onLoginSuccess={() => console.log('Login success')} />
</Suspense>
</footer>
</div>
);
}
export default App;5. 子应用组件实现
// 商品子应用 ProductList.tsx
import React from 'react';
import { productService } from '../services/productService';
interface ProductListProps {
onProductClick: (productId: string) => void;
}
export const ProductList: React.FC<ProductListProps> = ({ onProductClick }) => {
const [products, setProducts] = React.useState([]);
React.useEffect(() => {
productService.getProducts().then(setProducts);
}, []);
return (
<div className="product-list">
{products.map(product => (
<div
key={product.id}
className="product-item"
onClick={() => onProductClick(product.id)}
>
<h3>{product.name}</h3>
<p>¥{product.price}</p>
</div>
))}
</div>
);
};2.4.5 微前端通信机制
子应用之间的通信是微前端架构的关键问题,以下是几种常见的通信方案:
1. 基于事件总线的通信
// shared/eventBus.ts - 共享的事件总线
class EventBus {
private events: { [key: string]: Function[] } = {};
on(event: string, callback: Function) {
if (!this.events[event]) {
this.events[event] = [];
}
this.events[event].push(callback);
}
emit(event: string, data?: any) {
if (this.events[event]) {
this.events[event].forEach(callback => callback(data));
}
}
off(event: string, callback: Function) {
if (this.events[event]) {
this.events[event] = this.events[event].filter(cb => cb !== callback);
}
}
}
export const eventBus = new EventBus();
// 商品子应用发布事件
import { eventBus } from 'shared/eventBus';
eventBus.emit('product:selected', { productId: '123', name: 'iPhone 15' });
// 用户子应用订阅事件
import { eventBus } from 'shared/eventBus';
eventBus.on('product:selected', (data) => {
console.log('User selected product:', data);
// 更新用户浏览历史
});2. 基于共享状态的通信
// shared/store.ts - 共享状态管理
import { createStore } from 'redux';
interface AppState {
user: { id: string; name: string } | null;
cart: { productId: string; quantity: number }[];
}
const initialState: AppState = {
user: null,
cart: []
};
function appReducer(state = initialState, action: any) {
switch (action.type) {
case 'SET_USER':
return { ...state, user: action.payload };
case 'ADD_TO_CART':
return {
...state,
cart: [...state.cart, action.payload]
};
default:
return state;
}
}
export const sharedStore = createStore(appReducer);
// 用户子应用更新用户信息
import { sharedStore } from 'shared/store';
sharedStore.dispatch({
type: 'SET_USER',
payload: { id: '1', name: 'John' }
});
// 商品子应用读取用户信息
import { sharedStore } from 'shared/store';
const state = sharedStore.getState();
console.log('Current user:', state.user);3. 基于 Props 的通信
// 主应用传递 props 给子应用
import { ProductList } from 'productApp/ProductList';
function MainApp() {
const [selectedProduct, setSelectedProduct] = useState(null);
return (
<ProductList
user={{ id: '1', name: 'John' }}
onProductSelect={setSelectedProduct}
theme="dark"
/>
);
}2.4.6 微前端项目目录结构
以下是一个完整的微前端项目目录结构示例:
micro-frontend-project/
├── main-app/ # 主应用
│ ├── src/
│ │ ├── App.tsx
│ │ ├── Router.tsx
│ │ ├── layouts/
│ │ └── shared/ # 共享资源
│ │ ├── eventBus.ts
│ │ ├── store.ts
│ │ └── types.ts
│ ├── webpack.config.js
│ └── package.json
│
├── apps/ # 子应用目录
│ ├── product-app/ # 商品子应用
│ │ ├── src/
│ │ │ ├── components/
│ │ │ │ ├── ProductList.tsx
│ │ │ │ └── ProductDetail.tsx
│ │ │ ├── services/
│ │ │ │ └── productService.ts
│ │ │ └── bootstrap.tsx # 子应用入口
│ │ ├── webpack.config.js
│ │ └── package.json
│ │
│ ├── user-app/ # 用户子应用
│ │ ├── src/
│ │ │ ├── components/
│ │ │ │ ├── UserInfo.tsx
│ │ │ │ └── Login.tsx
│ │ │ ├── services/
│ │ │ │ └── userService.ts
│ │ │ └── bootstrap.tsx
│ │ ├── webpack.config.js
│ │ └── package.json
│ │
│ └── cart-app/ # 购物车子应用
│ ├── src/
│ ├── webpack.config.js
│ └── package.json
│
├── shared/ # 共享模块
│ ├── ui-components/ # 共享 UI 组件库
│ │ ├── src/
│ │ │ ├── Button/
│ │ │ ├── Modal/
│ │ │ └── index.ts
│ │ └── package.json
│ │
│ ├── utils/ # 共享工具库
│ │ ├── src/
│ │ │ ├── formatting.ts
│ │ │ ├── validation.ts
│ │ │ └── index.ts
│ │ └── package.json
│ │
│ └── types/ # 共享类型定义
│ ├── src/
│ │ ├── user.types.ts
│ │ ├── product.types.ts
│ │ └── index.ts
│ └── package.json
│
├── scripts/ # 构建和部署脚本
│ ├── build-all.sh
│ ├── start-all.sh
│ └── deploy.sh
│
├── docker/ # Docker 配置
│ ├── main-app.Dockerfile
│ ├── product-app.Dockerfile
│ └── docker-compose.yml
│
├── package.json # 根 package.json
└── README.md2.4.7 微前端最佳实践
在实际项目中应用微前端架构,建议遵循以下最佳实践:
| 策略要点 | 具体措施 |
|---|---|
| 合理划分子应用边界 | • 按业务领域划分:用户、商品、订单、支付等 • 按团队职责划分:每个团队负责一个或多个子应用 • 避免过度拆分:子应用数量不宜过多,建议 3-10 个 |
| 统一技术规范 | • 制定统一的编码规范和目录结构 • 统一 UI 组件库和设计系统 • 统一 API 接口规范和数据格式 |
| 共享依赖管理 | • 使用模块联邦的 shared 配置共享基础库 • 避免版本冲突,统一管理依赖版本 • 共享的依赖应该保持向后兼容 |
| 样式隔离策略 | • 使用 CSS Modules 或 CSS-in-JS • 使用命名空间前缀(如 product-app__button)• 使用 Shadow DOM 实现完全隔离 |
| 性能优化 | • 使用懒加载和预加载策略 • 合理配置共享依赖的加载时机 • 监控子应用的加载性能 |
| 错误处理 | • 子应用加载失败时的降级策略 • 全局错误捕获和上报 • 子应用之间的错误隔离 |
2.4.8 微前端小结
微前端架构是前端模块化发展的必然产物,它通过将大型前端应用拆分为多个独立的子应用,解决了以下核心问题:
复杂度控制:将复杂的单体应用拆分为多个简单的子应用,每个子应用专注于特定的业务领域,降低了系统的整体复杂度。
团队协作:不同团队可以独立开发和部署自己的子应用,互不影响,大大提高了开发效率和团队协作能力。
技术演进:子应用可以使用不同的技术栈,支持渐进式升级和技术迁移,避免了技术债务的积累。
可维护性:每个子应用都是独立的模块,可以独立测试、调试和维护,提高了代码的可维护性。
扩展性:新增业务功能时,只需添加新的子应用,无需修改现有代码,支持业务的快速扩展。
然而,微前端架构也带来了一些挑战:
- 架构复杂度:需要处理子应用的加载、通信、路由等问题
- 调试困难:跨子应用的调试和问题定位更加复杂
- 性能开销:多个子应用的加载可能影响首屏性能
- 团队要求:需要团队具备较强的架构能力和协作能力
因此,在决定采用微前端架构时,需要权衡项目的规模、团队的规模和技术能力。对于中小型项目,传统的单体应用可能更加合适;对于大型企业级应用,微前端架构则能够带来显著的收益。
核心建议:微前端不是目的,而是手段。在采用微前端架构之前,务必评估项目的实际需求,避免为了微前端而微前端。只有在真正需要解决复杂度、团队协作和技术演进问题时,微前端架构才能发挥其最大的价值。
结语
通过梳理一系列模块化编程的核心概念和实践技巧,我们旨在理解不同模块化规范的特点与适用场景,熟练掌握 ES Modules 的核心语法和最佳实践,并能够在前端和 Node.js 项目中灵活应用模块化设计。
模块化设计是构建大型、复杂应用的关键,它可以帮助开发者应对不断增长的应用复杂度,提高开发效率和代码质量。随着Web技术的不断发展,模块化设计将继续演进,与新兴技术深度融合,成为构建现代应用的核心原则。
希望本系列文章能够帮助读者理解模块化编程的重要性,掌握一些模块化设计的原则和实践技巧,构建出更健壮、更可维护的大型应用。