{"id":81,"date":"2026-06-15T10:45:31","date_gmt":"2026-06-15T02:45:31","guid":{"rendered":"https:\/\/yykcj.com\/?p=81"},"modified":"2026-06-15T10:45:31","modified_gmt":"2026-06-15T02:45:31","slug":"cqrs-%e6%a8%a1%e5%bc%8f%e5%ae%9e%e6%88%98%ef%bc%9a%e4%bb%8e%e7%90%86%e8%ae%ba%e5%88%b0%e4%bb%a3%e7%a0%81%ef%bc%8c%e6%9e%84%e5%bb%ba%e9%ab%98%e6%80%a7%e8%83%bd%e6%9f%a5%e8%af%a2%e7%b3%bb%e7%bb%9f","status":"publish","type":"post","link":"https:\/\/yykcj.com\/?p=81","title":{"rendered":"CQRS \u6a21\u5f0f\u5b9e\u6218\uff1a\u4ece\u7406\u8bba\u5230\u4ee3\u7801\uff0c\u6784\u5efa\u9ad8\u6027\u80fd\u67e5\u8be2\u7cfb\u7edf"},"content":{"rendered":"<h1>CQRS \u6a21\u5f0f\u5b9e\u6218\uff1a\u4ece\u7406\u8bba\u5230\u4ee3\u7801\uff0c\u6784\u5efa\u9ad8\u6027\u80fd\u67e5\u8be2\u7cfb\u7edf<\/h1>\n<h2>\u5f15\u8a00<\/h2>\n<p>\u5728\u4f20\u7edf\u7684 CRUD \u67b6\u6784\u4e2d\uff0c\u8bfb\u548c\u5199\u64cd\u4f5c\u5171\u7528\u540c\u4e00\u6570\u636e\u6a21\u578b\uff0c\u5bfc\u81f4\u7cfb\u7edf\u5728\u9ad8\u5e76\u53d1\u573a\u666f\u4e0b\u6027\u80fd\u74f6\u9888\u660e\u663e\u3002\u4f8b\u5982\uff0c\u7535\u5546\u8ba2\u5355\u7cfb\u7edf\u9700\u8981\u9891\u7e41\u67e5\u8be2\u8ba2\u5355\u72b6\u6001\uff0c\u540c\u65f6\u4e5f\u8981\u5904\u7406\u4e0b\u5355\u3001\u652f\u4ed8\u7b49\u5199\u64cd\u4f5c\u3002\u5982\u679c\u8bfb\u5199\u6df7\u7528\uff0c\u590d\u6742\u7684\u67e5\u8be2\u4f1a\u62d6\u6162\u5199\u64cd\u4f5c\u7684\u54cd\u5e94\u65f6\u95f4\u3002<\/p>\n<p>CQRS\uff08Command Query Responsibility Segregation\uff09\u6a21\u5f0f\u901a\u8fc7\u5206\u79bb\u547d\u4ee4\uff08\u5199\uff09\u548c\u67e5\u8be2\uff08\u8bfb\uff09\u7684\u804c\u8d23\uff0c\u5141\u8bb8\u5404\u81ea\u72ec\u7acb\u4f18\u5316\u3002\u672c\u6587\u5c06\u4ee5\u4e00\u4e2a\u7b80\u5316\u7248\u7684\u7535\u5546\u8ba2\u5355\u7cfb\u7edf\u4e3a\u4f8b\uff0c\u4ece\u96f6\u5f00\u59cb\u5b9e\u73b0 CQRS\uff0c\u5e76\u7ed3\u5408\u4e8b\u4ef6\u6eaf\u6e90\uff08Event Sourcing\uff09\u6765\u4fdd\u8bc1\u6570\u636e\u4e00\u81f4\u6027\u3002<\/p>\n<h2>\u9879\u76ee\u51c6\u5907<\/h2>\n<p>\u6211\u4eec\u4f7f\u7528 Node.js + TypeScript \u5b9e\u73b0\uff0c\u6570\u636e\u5e93\u9009\u7528 PostgreSQL\uff08\u5199\u5e93\uff09\u548c Redis\uff08\u8bfb\u5e93\u7f13\u5b58\uff09\u3002\u9996\u5148\u521d\u59cb\u5316\u9879\u76ee\uff1a<\/p>\n<pre><code class=\"language-bash\">\nmkdir cqrs-demo\ncd cqrs-demo\nnpm init -y\nnpm install typescript @types\/node ts-node pg redis uuid\n<\/code><\/pre>\n<p>\u521b\u5efa <code>tsconfig.json<\/code>\uff1a<\/p>\n<pre><code class=\"language-json\">\n{\n  &quot;compilerOptions&quot;: {\n    &quot;target&quot;: &quot;ES2020&quot;,\n    &quot;module&quot;: &quot;commonjs&quot;,\n    &quot;outDir&quot;: &quot;.\/dist&quot;,\n    &quot;rootDir&quot;: &quot;.\/src&quot;,\n    &quot;strict&quot;: true,\n    &quot;esModuleInterop&quot;: true\n  }\n}\n<\/code><\/pre>\n<h2>\u6838\u5fc3\u6982\u5ff5\u5b9e\u73b0<\/h2>\n<h3>1. \u547d\u4ee4\u4e0e\u547d\u4ee4\u5904\u7406\u5668<\/h3>\n<p>\u547d\u4ee4\uff08Command\uff09\u4ee3\u8868\u4e00\u4e2a\u5199\u64cd\u4f5c\uff0c\u5982\u521b\u5efa\u8ba2\u5355\u3002\u547d\u4ee4\u5904\u7406\u5668\uff08Command Handler\uff09\u8d1f\u8d23\u6267\u884c\u547d\u4ee4\u5e76\u4ea7\u751f\u4e8b\u4ef6\u3002<\/p>\n<pre><code class=\"language-typescript\">\n\/\/ src\/commands\/CreateOrderCommand.ts\nexport class CreateOrderCommand {\n  constructor(\n    public readonly orderId: string,\n    public readonly userId: string,\n    public readonly items: { productId: string; quantity: number }[],\n    public readonly totalAmount: number\n  ) {}\n}\n<\/code><\/pre>\n<pre><code class=\"language-typescript\">\n\/\/ src\/commands\/CreateOrderHandler.ts\nimport { CreateOrderCommand } from &#x27;.\/CreateOrderCommand&#x27;;\nimport { EventStore } from &#x27;..\/events\/EventStore&#x27;;\nimport { OrderCreatedEvent } from &#x27;..\/events\/OrderCreatedEvent&#x27;;\n\nexport class CreateOrderHandler {\n  constructor(private eventStore: EventStore) {}\n\n  async handle(command: CreateOrderCommand): Promise&lt;void&gt; {\n    \/\/ \u4e1a\u52a1\u6821\u9a8c\uff08\u7b80\u5316\uff09\n    if (!command.orderId || !command.userId) {\n      throw new Error(&#x27;Invalid command&#x27;);\n    }\n\n    \/\/ \u751f\u6210\u4e8b\u4ef6\n    const event = new OrderCreatedEvent(\n      command.orderId,\n      command.userId,\n      command.items,\n      command.totalAmount,\n      new Date()\n    );\n\n    \/\/ \u4fdd\u5b58\u4e8b\u4ef6\u5230\u4e8b\u4ef6\u5b58\u50a8\n    await this.eventStore.save(event);\n  }\n}\n<\/code><\/pre>\n<h3>2. \u4e8b\u4ef6\u4e0e\u4e8b\u4ef6\u5b58\u50a8<\/h3>\n<p>\u4e8b\u4ef6\uff08Event\uff09\u8868\u793a\u5df2\u7ecf\u53d1\u751f\u7684\u4e8b\u60c5\uff0c\u662f\u4e0d\u53ef\u53d8\u7684\u3002\u4e8b\u4ef6\u5b58\u50a8\uff08Event Store\uff09\u8d1f\u8d23\u6301\u4e45\u5316\u4e8b\u4ef6\uff0c\u5e76\u652f\u6301\u6309\u805a\u5408ID\u67e5\u8be2\u3002<\/p>\n<pre><code class=\"language-typescript\">\n\/\/ src\/events\/OrderCreatedEvent.ts\nexport class OrderCreatedEvent {\n  constructor(\n    public readonly orderId: string,\n    public readonly userId: string,\n    public readonly items: { productId: string; quantity: number }[],\n    public readonly totalAmount: number,\n    public readonly createdAt: Date\n  ) {}\n\n  get eventType(): string {\n    return &#x27;OrderCreated&#x27;;\n  }\n}\n<\/code><\/pre>\n<pre><code class=\"language-typescript\">\n\/\/ src\/events\/EventStore.ts\nimport { Pool } from &#x27;pg&#x27;;\n\nexport class EventStore {\n  constructor(private pool: Pool) {}\n\n  async save(event: any): Promise&lt;void&gt; {\n    const query = `\n      INSERT INTO events (aggregate_id, event_type, event_data, created_at)\n      VALUES ($1, $2, $3, $4)\n    `;\n    const values = [\n      event.orderId,\n      event.eventType,\n      JSON.stringify(event),\n      event.createdAt\n    ];\n    await this.pool.query(query, values);\n  }\n\n  async getEventsByAggregateId(aggregateId: string): Promise&lt;any[]&gt; {\n    const result = await this.pool.query(\n      &#x27;SELECT event_data FROM events WHERE aggregate_id = $1 ORDER BY created_at&#x27;,\n      [aggregateId]\n    );\n    return result.rows.map(row =&gt; JSON.parse(row.event_data));\n  }\n}\n<\/code><\/pre>\n<p>\u521b\u5efa\u4e8b\u4ef6\u8868\uff1a<\/p>\n<pre><code class=\"language-sql\">\nCREATE TABLE events (\n  id SERIAL PRIMARY KEY,\n  aggregate_id VARCHAR(255) NOT NULL,\n  event_type VARCHAR(255) NOT NULL,\n  event_data JSONB NOT NULL,\n  created_at TIMESTAMP NOT NULL\n);\nCREATE INDEX idx_aggregate_id ON events (aggregate_id);\n<\/code><\/pre>\n<h3>3. \u8bfb\u6a21\u578b\u4e0e\u6295\u5f71<\/h3>\n<p>\u8bfb\u6a21\u578b\uff08Read Model\uff09\u662f\u4e13\u4e3a\u67e5\u8be2\u4f18\u5316\u7684\u6570\u636e\u89c6\u56fe\u3002\u6295\u5f71\uff08Projection\uff09\u8d1f\u8d23\u6d88\u8d39\u4e8b\u4ef6\u5e76\u66f4\u65b0\u8bfb\u6a21\u578b\u3002\u6211\u4eec\u4f7f\u7528 Redis \u4f5c\u4e3a\u7f13\u5b58\uff0c\u540c\u65f6\u7ef4\u62a4\u4e00\u4e2a PostgreSQL \u8bfb\u5e93\u8868\u3002<\/p>\n<pre><code class=\"language-typescript\">\n\/\/ src\/readmodels\/OrderReadModel.ts\nexport interface OrderReadModel {\n  orderId: string;\n  userId: string;\n  items: { productId: string; quantity: number }[];\n  totalAmount: number;\n  status: string;\n  createdAt: Date;\n}\n<\/code><\/pre>\n<pre><code class=\"language-typescript\">\n\/\/ src\/projections\/OrderProjection.ts\nimport { EventStore } from &#x27;..\/events\/EventStore&#x27;;\nimport { Redis } from &#x27;ioredis&#x27;;\nimport { Pool } from &#x27;pg&#x27;;\n\nexport class OrderProjection {\n  constructor(\n    private eventStore: EventStore,\n    private redis: Redis,\n    private readDb: Pool\n  ) {}\n\n  async project(event: any): Promise&lt;void&gt; {\n    if (event.eventType === &#x27;OrderCreated&#x27;) {\n      await this.handleOrderCreated(event);\n    }\n  }\n\n  private async handleOrderCreated(event: any): Promise&lt;void&gt; {\n    const readModel = {\n      orderId: event.orderId,\n      userId: event.userId,\n      items: event.items,\n      totalAmount: event.totalAmount,\n      status: &#x27;created&#x27;,\n      createdAt: event.createdAt\n    };\n\n    \/\/ \u66f4\u65b0 Redis \u7f13\u5b58\n    await this.redis.set(\n      `order:${event.orderId}`,\n      JSON.stringify(readModel),\n      &#x27;EX&#x27;,\n      3600\n    );\n\n    \/\/ \u66f4\u65b0 PostgreSQL \u8bfb\u5e93\n    await this.readDb.query(\n      `INSERT INTO order_read (order_id, user_id, items, total_amount, status, created_at)\n       VALUES ($1, $2, $3, $4, $5, $6)\n       ON CONFLICT (order_id) DO UPDATE SET\n         items = EXCLUDED.items,\n         total_amount = EXCLUDED.total_amount,\n         status = EXCLUDED.status`,\n      [\n        readModel.orderId,\n        readModel.userId,\n        JSON.stringify(readModel.items),\n        readModel.totalAmount,\n        readModel.status,\n        readModel.createdAt\n      ]\n    );\n  }\n}\n<\/code><\/pre>\n<h3>4. \u67e5\u8be2\u5904\u7406\u5668<\/h3>\n<p>\u67e5\u8be2\u5904\u7406\u5668\uff08Query Handler\uff09\u8d1f\u8d23\u5904\u7406\u8bfb\u8bf7\u6c42\uff0c\u76f4\u63a5\u4ece\u8bfb\u6a21\u578b\u8fd4\u56de\u6570\u636e\u3002<\/p>\n<pre><code class=\"language-typescript\">\n\/\/ src\/queries\/GetOrderQuery.ts\nexport class GetOrderQuery {\n  constructor(public readonly orderId: string) {}\n}\n<\/code><\/pre>\n<pre><code class=\"language-typescript\">\n\/\/ src\/queries\/GetOrderHandler.ts\nimport { Redis } from &#x27;ioredis&#x27;;\nimport { Pool } from &#x27;pg&#x27;;\nimport { OrderReadModel } from &#x27;..\/readmodels\/OrderReadModel&#x27;;\n\nexport class GetOrderHandler {\n  constructor(\n    private redis: Redis,\n    private readDb: Pool\n  ) {}\n\n  async handle(query: GetOrderQuery): Promise&lt;OrderReadModel | null&gt; {\n    \/\/ \u5148\u4ece\u7f13\u5b58\u8bfb\u53d6\n    const cached = await this.redis.get(`order:${query.orderId}`);\n    if (cached) {\n      return JSON.parse(cached);\n    }\n\n    \/\/ \u7f13\u5b58\u672a\u547d\u4e2d\uff0c\u4ece\u8bfb\u5e93\u8bfb\u53d6\n    const result = await this.readDb.query(\n      &#x27;SELECT * FROM order_read WHERE order_id = $1&#x27;,\n      [query.orderId]\n    );\n\n    if (result.rows.length === 0) {\n      return null;\n    }\n\n    const row = result.rows[0];\n    const readModel: OrderReadModel = {\n      orderId: row.order_id,\n      userId: row.user_id,\n      items: JSON.parse(row.items),\n      totalAmount: row.total_amount,\n      status: row.status,\n      createdAt: row.created_at\n    };\n\n    \/\/ \u5199\u5165\u7f13\u5b58\n    await this.redis.set(\n      `order:${query.orderId}`,\n      JSON.stringify(readModel),\n      &#x27;EX&#x27;,\n      3600\n    );\n\n    return readModel;\n  }\n}\n<\/code><\/pre>\n<h2>\u7ec4\u88c5\u4e0e\u8fd0\u884c<\/h2>\n<h3>1. \u4f9d\u8d56\u6ce8\u5165\u4e0e\u8def\u7531<\/h3>\n<p>\u4f7f\u7528\u7b80\u5355\u7684\u5de5\u5382\u6a21\u5f0f\u7ec4\u88c5\u7ec4\u4ef6\uff1a<\/p>\n<pre><code class=\"language-typescript\">\n\/\/ src\/index.ts\nimport { Pool } from &#x27;pg&#x27;;\nimport Redis from &#x27;ioredis&#x27;;\nimport { EventStore } from &#x27;.\/events\/EventStore&#x27;;\nimport { CreateOrderHandler } from &#x27;.\/commands\/CreateOrderHandler&#x27;;\nimport { GetOrderHandler } from &#x27;.\/queries\/GetOrderHandler&#x27;;\nimport { OrderProjection } from &#x27;.\/projections\/OrderProjection&#x27;;\n\nasync function main() {\n  \/\/ \u521d\u59cb\u5316\u6570\u636e\u5e93\u8fde\u63a5\n  const writeDb = new Pool({ connectionString: &#x27;postgres:\/\/user:pass@localhost:5432\/write_db&#x27; });\n  const readDb = new Pool({ connectionString: &#x27;postgres:\/\/user:pass@localhost:5432\/read_db&#x27; });\n  const redis = new Redis({ host: &#x27;localhost&#x27;, port: 6379 });\n\n  const eventStore = new EventStore(writeDb);\n  const createOrderHandler = new CreateOrderHandler(eventStore);\n  const getOrderHandler = new GetOrderHandler(redis, readDb);\n  const projection = new OrderProjection(eventStore, redis, readDb);\n\n  \/\/ \u6a21\u62df\u4e8b\u4ef6\u5904\u7406\uff08\u751f\u4ea7\u73af\u5883\u5e94\u4f7f\u7528\u6d88\u606f\u961f\u5217\uff09\n  \/\/ \u5b9e\u9645\u9879\u76ee\u4e2d\uff0c\u4e8b\u4ef6\u5b58\u50a8\u540e\u5e94\u53d1\u5e03\u5230\u6d88\u606f\u961f\u5217\uff0c\u7531\u6295\u5f71\u6d88\u8d39\n  \/\/ \u8fd9\u91cc\u7b80\u5316\uff1a\u5728\u547d\u4ee4\u5904\u7406\u5668\u4e2d\u76f4\u63a5\u8c03\u7528\u6295\u5f71\n  \/\/ \u6ce8\u610f\uff1a\u66f4\u597d\u7684\u505a\u6cd5\u662f\u5f02\u6b65\u89e3\u8026\n\n  \/\/ \u793a\u4f8b\uff1a\u521b\u5efa\u8ba2\u5355\n  const command = new CreateOrderCommand(&#x27;order-123&#x27;, &#x27;user-456&#x27;, [\n    { productId: &#x27;prod-1&#x27;, quantity: 2 }\n  ], 99.99);\n  await createOrderHandler.handle(command);\n\n  \/\/ \u624b\u52a8\u89e6\u53d1\u6295\u5f71\uff08\u5b9e\u9645\u5e94\u5f02\u6b65\uff09\n  const events = await eventStore.getEventsByAggregateId(&#x27;order-123&#x27;);\n  for (const event of events) {\n    await projection.project(event);\n  }\n\n  \/\/ \u67e5\u8be2\u8ba2\u5355\n  const query = new GetOrderQuery(&#x27;order-123&#x27;);\n  const order = await getOrderHandler.handle(query);\n  console.log(&#x27;Order:&#x27;, order);\n}\n\nmain().catch(console.error);\n<\/code><\/pre>\n<p>> <strong>\u6ce8\u610f<\/strong>\uff1a\u4e0a\u8ff0\u4ee3\u7801\u4e2d\uff0c\u6295\u5f71\u662f\u540c\u6b65\u8c03\u7528\u7684\uff0c\u4ec5\u7528\u4e8e\u6f14\u793a\u3002\u751f\u4ea7\u73af\u5883\u4e2d\uff0c\u4e8b\u4ef6\u5b58\u50a8\u540e\u5e94\u901a\u8fc7\u6d88\u606f\u961f\u5217\uff08\u5982 RabbitMQ\u3001Kafka\uff09\u5f02\u6b65\u53d1\u5e03\u4e8b\u4ef6\uff0c\u7531\u72ec\u7acb\u7684\u6295\u5f71\u670d\u52a1\u6d88\u8d39\uff0c\u4ee5\u907f\u514d\u5199\u8def\u5f84\u963b\u585e\u3002<\/p>\n<h3>2. \u6570\u636e\u5e93\u521d\u59cb\u5316\u811a\u672c<\/h3>\n<p>\u521b\u5efa\u8bfb\u5e93\u8868\uff1a<\/p>\n<pre><code class=\"language-sql\">\nCREATE TABLE order_read (\n  order_id VARCHAR(255) PRIMARY KEY,\n  user_id VARCHAR(255) NOT NULL,\n  items JSONB NOT NULL,\n  total_amount DECIMAL(10,2) NOT NULL,\n  status VARCHAR(50) NOT NULL DEFAULT &#x27;created&#x27;,\n  created_at TIMESTAMP NOT NULL\n);\n<\/code><\/pre>\n<h2>\u8fdb\u9636\u4f18\u5316\uff1a\u4e8b\u4ef6\u6eaf\u6e90\u4e0e\u805a\u5408\u6839<\/h2>\n<p>\u5728 CQRS \u4e2d\uff0c\u805a\u5408\u6839\uff08Aggregate Root\uff09\u8d1f\u8d23\u4fdd\u8bc1\u4e1a\u52a1\u4e00\u81f4\u6027\u3002\u4e8b\u4ef6\u6eaf\u6e90\uff08Event Sourcing\uff09\u5c06\u805a\u5408\u7684\u72b6\u6001\u5b58\u50a8\u4e3a\u4e00\u7cfb\u5217\u4e8b\u4ef6\uff0c\u91cd\u5efa\u72b6\u6001\u65f6\u56de\u653e\u4e8b\u4ef6\u3002<\/p>\n<h3>\u5b9e\u73b0\u4e00\u4e2a\u7b80\u5355\u7684\u8ba2\u5355\u805a\u5408\u6839<\/h3>\n<pre><code class=\"language-typescript\">\n\/\/ src\/aggregates\/OrderAggregate.ts\nimport { OrderCreatedEvent } from &#x27;..\/events\/OrderCreatedEvent&#x27;;\n\nexport class OrderAggregate {\n  public orderId: string = &#x27;&#x27;;\n  public userId: string = &#x27;&#x27;;\n  public status: string = &#x27;&#x27;;\n\n  \/\/ \u4ece\u4e8b\u4ef6\u91cd\u5efa\u72b6\u6001\n  static fromEvents(events: any[]): OrderAggregate {\n    const aggregate = new OrderAggregate();\n    for (const event of events) {\n      aggregate.apply(event);\n    }\n    return aggregate;\n  }\n\n  apply(event: any): void {\n    if (event.eventType === &#x27;OrderCreated&#x27;) {\n      this.orderId = event.orderId;\n      this.userId = event.userId;\n      this.status = &#x27;created&#x27;;\n    }\n    \/\/ \u5176\u4ed6\u4e8b\u4ef6\u5904\u7406...\n  }\n}\n<\/code><\/pre>\n<p>\u8fd9\u6837\uff0c\u547d\u4ee4\u5904\u7406\u5668\u53ef\u4ee5\u4ece\u4e8b\u4ef6\u5b58\u50a8\u4e2d\u83b7\u53d6\u6240\u6709\u4e8b\u4ef6\uff0c\u91cd\u5efa\u805a\u5408\u72b6\u6001\uff0c\u7136\u540e\u6267\u884c\u4e1a\u52a1\u903b\u8f91\u5e76\u4ea7\u751f\u65b0\u4e8b\u4ef6\u3002<\/p>\n<h2>\u5e38\u89c1\u5751\u4e0e\u6700\u4f73\u5b9e\u8df5<\/h2>\n<ol>\n<li><strong>\u6700\u7ec8\u4e00\u81f4\u6027<\/strong>\uff1aCQRS \u8bfb\u6a21\u578b\u662f\u6700\u7ec8\u4e00\u81f4\u7684\uff0c\u4ece\u5199\u547d\u4ee4\u5230\u8bfb\u6a21\u578b\u66f4\u65b0\u5b58\u5728\u5ef6\u8fdf\u3002\u5bf9\u4e8e\u9700\u8981\u5f3a\u4e00\u81f4\u7684\u573a\u666f\uff08\u5982\u652f\u4ed8\uff09\uff0c\u5e94\u76f4\u63a5\u4ece\u5199\u5e93\u8bfb\u53d6\u6216\u4f7f\u7528\u540c\u6b65\u6295\u5f71\u3002<\/li>\n<li><strong>\u4e8b\u4ef6\u7248\u672c\u7ba1\u7406<\/strong>\uff1a\u4e8b\u4ef6\u7ed3\u6784\u53ef\u80fd\u53d8\u5316\uff0c\u5efa\u8bae\u4e3a\u4e8b\u4ef6\u6dfb\u52a0\u7248\u672c\u53f7\uff0c\u5e76\u7f16\u5199\u8fc1\u79fb\u811a\u672c\u3002<\/li>\n<li><strong>\u907f\u514d\u8fc7\u5ea6\u8bbe\u8ba1<\/strong>\uff1a\u5e76\u975e\u6240\u6709\u7cfb\u7edf\u90fd\u9700\u8981 CQRS\u3002\u5f53\u4f60\u7684\u7cfb\u7edf\u8bfb\u5199\u8d1f\u8f7d\u4e0d\u5747\u8861\uff0c\u6216\u67e5\u8be2\u590d\u6742\u5ea6\u8fdc\u8d85\u5199\u5165\u65f6\uff0c\u624d\u8003\u8651\u5f15\u5165\u3002<\/li>\n<li><strong>\u6d88\u606f\u961f\u5217\u53ef\u9760\u6027<\/strong>\uff1a\u4e8b\u4ef6\u53d1\u5e03\u5230\u6d88\u606f\u961f\u5217\u65f6\uff0c\u9700\u4fdd\u8bc1\u81f3\u5c11\u4e00\u6b21\u6295\u9012\uff0c\u5e76\u5904\u7406\u91cd\u590d\u4e8b\u4ef6\uff08\u5e42\u7b49\u6027\uff09\u3002<\/li>\n<\/ol>\n<h2>\u603b\u7ed3<\/h2>\n<p>\u672c\u6587\u901a\u8fc7\u4e00\u4e2a\u7535\u5546\u8ba2\u5355\u7cfb\u7edf\uff0c\u5b8c\u6574\u5b9e\u73b0\u4e86 CQRS \u6a21\u5f0f\uff0c\u5305\u62ec\u547d\u4ee4\u3001\u4e8b\u4ef6\u3001\u6295\u5f71\u548c\u67e5\u8be2\u3002CQRS \u5c06\u8bfb\u5199\u804c\u8d23\u5206\u79bb\uff0c\u4f7f\u7cfb\u7edf\u66f4\u7075\u6d3b\u3001\u53ef\u6269\u5c55\u3002\u7ed3\u5408\u4e8b\u4ef6\u6eaf\u6e90\uff0c\u8fd8\u80fd\u83b7\u5f97\u5b8c\u6574\u7684\u5ba1\u8ba1\u65e5\u5fd7\u3002<\/p>\n<p>\u4e0b\u4e00\u6b65\uff0c\u4f60\u53ef\u4ee5\u5c1d\u8bd5\uff1a<\/p>\n<ul>\n<li>\u5f15\u5165\u6d88\u606f\u961f\u5217\uff08\u5982 RabbitMQ\uff09\u5f02\u6b65\u89e3\u8026\u547d\u4ee4\u548c\u6295\u5f71<\/li>\n<li>\u5b9e\u73b0\u66f4\u590d\u6742\u7684\u4e1a\u52a1\u4e8b\u4ef6\uff08\u5982\u8ba2\u5355\u53d6\u6d88\u3001\u652f\u4ed8\u6210\u529f\uff09<\/li>\n<li>\u90e8\u7f72\u5230\u4e91\u73af\u5883\uff0c\u6d4b\u8bd5\u8bfb\u5199\u5206\u79bb\u7684\u6027\u80fd\u63d0\u5347<\/li>\n<\/ul>\n<p>\u5e0c\u671b\u8fd9\u7bc7\u6587\u7ae0\u80fd\u5e2e\u52a9\u4f60\u638c\u63e1 CQRS \u7684\u5b9e\u6218\u5e94\u7528\uff0c\u6784\u5efa\u66f4\u5065\u58ee\u7684\u7cfb\u7edf\u3002<\/p>\n","protected":false},"excerpt":{"rendered":"<p>\u672c\u6587\u901a\u8fc7\u4e00\u4e2a\u7535\u5546\u8ba2\u5355\u7cfb\u7edf\u7684\u5b9e\u6218\u6848\u4f8b\uff0c\u624b\u628a\u624b\u6559\u4f60\u5b9e\u73b0CQRS\u6a21\u5f0f\uff0c\u5206\u79bb\u547d\u4ee4\u548c\u67e5\u8be2\u804c\u8d23\uff0c\u63d0\u5347\u7cfb\u7edf\u6027\u80fd\u548c\u53ef\u7ef4\u62a4\u6027\u3002\u6db5\u76d6\u4e8b\u4ef6\u6eaf\u6e90\u3001\u8bfb\u5199\u5206\u79bb\u7b49\u6838\u5fc3\u6982\u5ff5\uff0c\u9644\u5e26\u5b8c\u6574TypeScript\u4ee3\u7801\u793a\u4f8b\u3002<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[3,4],"tags":[38,5,40,39,41],"class_list":["post-81","post","type-post","status-publish","format-standard","hentry","category-3","category-4","tag-cqrs","tag-node-js","tag-typescript","tag-39","tag-41"],"_links":{"self":[{"href":"https:\/\/yykcj.com\/index.php?rest_route=\/wp\/v2\/posts\/81","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/yykcj.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/yykcj.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/yykcj.com\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/yykcj.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=81"}],"version-history":[{"count":0,"href":"https:\/\/yykcj.com\/index.php?rest_route=\/wp\/v2\/posts\/81\/revisions"}],"wp:attachment":[{"href":"https:\/\/yykcj.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=81"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/yykcj.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=81"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/yykcj.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=81"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}