CQRS和事件溯源在TypeScript中的应用:复杂系统的架构

面向初学者的在线Python训练器

轻松学习Python,无需理论负担。在浏览器中直接解决带自动检查的实践任务,无需安装任何东西。

开始课程

为什么当业务变得复杂时,普通的CRUD应用会失效

\n\n

你是否曾遇到过这样的情况:一个简单的任务——"显示订单变更历史"——却变成了噩梦?你翻遍数据库,查看日志,试图还原是谁在什么时候更改了状态。然后新的需求来了:"请审计用户过去三年的所有操作"。你意识到——当前的架构无法承受这种压力。

\n\n

大家好,我是PythonLib,今天我们将深入探讨两种强大的模式,它们能在这种情况下拯救你:CQRS(命令查询职责分离)和事件溯源。我们将用TypeScript编写代码,这些代码可以直接用于实际项目。

\n\n

如果你正在开发金融科技、电商平台、CRM或任何重视历史记录和审计的系统——这篇文章就是为你准备的。

\n\n

用简单的话解释什么是CQRS

\n\n

想象一个图书馆。通常,一位图书管理员既负责借书,也负责还书和登记。但当访客增多时,就会排起长队。CQRS建议将职责分开:一位图书管理员只负责借出(命令),另一位只负责接收和登记(查询)。

\n\n

在编程中,这意味着将读取和写入的模型分离。我们不再使用单一的存储库,而是创建:

\n\n
    \n
  • 命令模型 — 用于修改数据(创建、更新、删除)
  • \n
  • 查询模型 — 用于读取数据(读取)
  • \n
\n\n

这看起来像是增加了复杂性?是的,在初期。但当系统发展壮大时,你会获得灵活性:可以分别优化读取和写入,使用不同的数据库,以及缓存。

\n\n

TypeScript示例:不带事件溯源的简单CQRS

\n\n

假设我们有一个订单管理系统。首先定义实体:

\n\n
// domain/order.ts\nexport interface Order {\n  id: string;\n  userId: string;\n  items: OrderItem[];\n  status: 'pending' | 'confirmed' | 'shipped' | 'cancelled';\n  total: number;\n  createdAt: Date;\n  updatedAt: Date;\n}\n\nexport interface OrderItem {\n  productId: string;\n  quantity: number;\n  price: number;\n}\n
\n\n

现在创建命令和查询:

\n\n
// application/commands/create-order.command.ts\nexport class CreateOrderCommand {\n  constructor(\n    public readonly userId: string,\n    public readonly items: OrderItem[]\n  ) {}\n}\n\n// application/commands/create-order.handler.ts\nimport { CommandHandler, ICommandHandler } from '@nestjs/cqrs';\nimport { OrderRepository } from '../repositories/order.repository';\n\n@CommandHandler(CreateOrderCommand)\nexport class CreateOrderHandler implements ICommandHandler<CreateOrderCommand> {\n  constructor(private readonly orderRepo: OrderRepository) {}\n\n  async execute(command: CreateOrderCommand): Promise<string> {\n    const order: Order = {\n      id: uuid(),\n      userId: command.userId,\n      items: command.items,\n      status: 'pending',\n      total: command.items.reduce((sum, item) => sum + item.price * item.quantity, 0),\n      createdAt: new Date(),\n      updatedAt: new Date()\n    };\n    await this.orderRepo.save(order);\n    return order.id;\n  }\n}\n
\n\n

而对于读取,则使用单独的处理程序:

\n\n
// application/queries/get-order.query.ts\nexport class GetOrderQuery {\n  constructor(public readonly orderId: string) {}\n}\n\n// application/queries/get-order.handler.ts\nimport { QueryHandler, IQueryHandler } from '@nestjs/cqrs';\nimport { OrderReadRepository } from '../repositories/order-read.repository';\n\n@QueryHandler(GetOrderQuery)\nexport class GetOrderHandler implements IQueryHandler<GetOrderQuery> {\n  constructor(private readonly readRepo: OrderReadRepository) {}\n\n  async execute(query: GetOrderQuery): Promise<Order | null> {\n    return this.readRepo.findById(query.orderId);\n  }\n}\n                

博客

书籍推荐