为什么当业务变得复杂时,普通的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\nTypeScript示例:不带事件溯源的简单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