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

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

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

开始课程

为什么CQRS和事件溯源不是“精英专属”?

\n\n

当我们听到“CQRS”或“事件溯源”这些术语时,许多JavaScript开发者会联想到Java或C#企业级架构中的某些东西。复杂的方案、无穷无尽的事件、奇怪的存储……但如果我告诉你,这些模式可以(并且应该!)在普通的Node.js应用程序中使用呢?

\n\n

在本文中,我们将探讨CQRS和事件溯源如何帮助解决实际问题:数据冲突、复杂日志记录、扩展和调试。而且这一切——都基于纯JavaScript,无需魔法或复杂的框架。

\n\n

什么是CQRS(命令查询职责分离)?

\n\n

想象一下,你的应用程序就像一家餐厅。服务员(查询)和厨师(命令)在同一个空间工作。迟早他们会互相干扰。CQRS建议将这些区域分开:服务员在大厅工作(读取),而厨师在厨房工作(写入)。

\n\n

CQRS 是一种将操作分为两个通道的原则:

\n
    \n
  • 命令(Commands) — 改变系统状态(创建、更新、删除)
  • \n
  • 查询(Queries) — 仅读取数据(读取)
  • \n
\n\n

在经典的REST API中,我们通常使用同一个对象进行读写。例如,UserController包含方法GET /users/:id和POST /users。CQRS建议将它们分散到不同的层中。

\n\n

JavaScript中CQRS的简单示例

\n\n
// 命令(改变状态)\nclass CreateUserCommand {\n    constructor(name, email) {\n        this.name = name;\n        this.email = email;\n    }\n}\n\nclass UserCommandHandler {\n    async handle(command) {\n        // 验证、业务逻辑、保存\n        return await db.users.insert({\n            name: command.name,\n            email: command.email,\n            created_at: new Date()\n        });\n    }\n}\n\n// 查询(仅读取)\nclass GetUserQuery {\n    constructor(userId) {\n        this.userId = userId;\n    }\n}\n\nclass UserQueryHandler {\n    async handle(query) {\n        // 仅读取,不做任何更改!\n        return await db.users.findOne({ _id: query.userId });\n    }\n}
\n\n

这看起来像是在让代码变得更复杂?是的,乍一看确实如此。但当你的应用程序增长到50多个端点时,命令和查询的分离将帮助你避免:

\n
    \n
  • 在GET请求中意外修改数据
  • \n
  • 缓存问题(查询易于缓存,命令则不然)
  • \n
  • 多名开发者并行工作时产生的冲突
  • \n
\n\n

事件溯源:记住每一个操作

\n\n

现在想象一下,与其存储用户的当前状态,不如存储所有发生在他们身上的事件。不是“伊万,25岁,莫斯科”,而是一个序列:

\n
    \n
  1. “用户已创建:伊万”
  2. \n
  3. “用户将年龄改为25”
  4. \n
  5. “用户搬到了莫斯科”
  6. \n
\n\n

事件溯源 是一种模式,其中每个状态更改都作为不可变事件保存。当前状态通过“重放”所有事件来计算。

\n\n

在JavaScript中实现事件存储

\n\n
class EventStore {\n    constructor() {\n        this.events = [];\n    }\n\n    append(aggregateId, event) {\n        this.events.push({\n            aggregateId,\n            event,\n            timestamp: new Date()\n        });\n    }\n\n    getEvents(aggregateId) {\n        return this.events.filter(e => e.aggregateId === aggregateId);\n    }\n}\n\n// 从事件重建状态\nfunction rebuildState(events) {\n    return events.reduce((state, event) => {\n        switch(event.event.type) {\n            case 'USER_CREATED':\n                return { ...state, name: event.event.name, age: 0 };\n            case                

博客

书籍推荐