如何在 .NET Core 中实现 CQRS(命令查询职责分离)模式:全面讲解与实战代码

news/2025/2/25 8:15:56/文章来源:https://www.cnblogs.com/forges/p/18735364

命令查询职责分离(CQRS,Command Query Responsibility Segregation)是一种架构模式,它将系统中的写操作(即修改数据的命令操作)与读操作(即查询数据的操作)分离开来。CQRS 模式能够提升系统的可伸缩性、性能和可维护性,尤其适用于复杂的业务场景和高并发的系统。在传统的 CRUD(增、删、改、查)架构中,读写操作通常共享同一数据模型,而 CQRS 将这两者彻底分开,让它们有独立的模型、接口和存储方式。

本文将深入探讨 CQRS 的概念、优缺点,并通过一个基于 .NET Core 的代码示例,详细讲解如何实现 CQRS。

一、CQRS 概述

CQRS 的核心思想是将 命令(修改数据)和 查询(读取数据)的职责进行分离,从而实现更加高效的性能优化和可扩展性。在传统架构中,写操作和读操作往往是通过同一个数据模型和存储进行处理的,但在 CQRS 中,写和读的模型和接口是独立的,这样可以针对读操作和写操作分别进行优化,达到更高的性能和灵活性。

1.1 CQRS 的基本原理

  • 命令(Command):表示对系统状态进行改变的请求(如创建、更新、删除)。它不应该返回任何数据,而是通过影响系统的状态来改变数据。命令一般通过命令处理器(Command Handler)进行处理。

  • 查询(Query):表示请求从系统中读取数据。查询操作不会改变系统的状态,它只会从数据库或缓存中获取数据。查询处理器(Query Handler)负责执行查询操作。

1.2 CQRS 的工作方式

CQRS 将应用的读和写操作分离成不同的模型,这意味着:

  • 命令模型:专注于处理“写”操作(如新增、更新、删除数据),并确保数据的一致性和有效性。
  • 查询模型:专注于处理“读”操作,优化数据查询的性能,可能会采用不同的存储结构或缓存策略。

1.3 典型应用场景

CQRS 适用于以下几种场景:

  • 高并发系统:读请求远多于写请求,如电商网站、社交网络等。
  • 复杂业务规则:当写操作的业务逻辑非常复杂时,可以将写操作独立出来,确保维护性和灵活性。
  • 性能优化:CQRS 可以针对读取操作进行优化,例如使用缓存、NoSQL 数据库等来提升查询性能。

二、CQRS 的优势与挑战

2.1 优势

  1. 提高可伸缩性

    • 由于读写操作分开,可以单独对查询和命令进行扩展。如果查询请求量比写请求量大,可以单独扩展查询服务,提高系统的响应能力。
  2. 查询优化

    • 查询模型可以专门针对读取操作进行优化。例如,可以使用不同的数据存储方案(如 NoSQL 数据库、全文搜索引擎等)来提高查询效率。
    • 还可以使用缓存来减少对数据库的频繁访问,从而提高性能。
  3. 解耦与清晰的职责

    • 读写操作被分开,每个模型和服务都有单一的职责。这使得代码的维护性更强,理解和扩展变得更加容易。
  4. 灵活的数据库选择

    • 写操作和读操作可以分别使用不同的存储技术。例如,写操作可以使用关系型数据库(如 SQL Server),而查询操作则可以使用 NoSQL 数据库(如 MongoDB、Cassandra)或搜索引擎(如 Elasticsearch)。
  5. 支持最终一致性

    • CQRS 模式特别适合与事件溯源(Event Sourcing)一起使用,可以通过事件追溯系统的状态变化,确保数据的一致性。

2.2 挑战

  1. 系统复杂度增加

    • CQRS 使得系统设计和实现的复杂度提高。读写模型的分离、数据同步、事件处理等都需要额外的开发和维护工作。
  2. 数据一致性问题

    • 由于读写操作使用不同的模型和存储,可能会出现短期内的数据不一致性。通常需要通过异步机制(如消息队列、事件溯源)来保证最终一致性。
  3. 开发和维护成本

    • 实现 CQRS 需要更细粒度的控制和更多的代码,包括命令处理器、查询处理器、多个数据存储的管理等,导致开发和维护成本增加。

三、如何在 .NET Core 中实现 CQRS

在本节中,我们将通过一个简单的博客系统的例子,演示如何使用 .NET Core 实现 CQRS。

3.1 项目结构

假设我们的博客系统需要支持以下功能:

  • 创建博客文章(命令操作)。
  • 获取单篇博客文章(查询操作)。
  • 获取所有博客文章(查询操作)。

项目结构如下:

CQRSExample/
├── Application/
│   ├── Commands/
│   │   ├── CreatePostCommand.cs
│   │   └── CreatePostCommandHandler.cs
│   ├── Queries/
│   │   ├── GetPostQuery.cs
│   │   ├── GetAllPostsQuery.cs
│   │   └── GetPostQueryHandler.cs
│   │   └── GetAllPostsQueryHandler.cs
├── Domain/
│   └── Post.cs
├── Infrastructure/
│   └── PostRepository.cs
├── Web/
│   └── Controllers/
│       └── PostsController.cs
└── CQRSExample.sln

3.2 代码实现

3.2.1 命令部分(Command)

我们首先定义一个命令 CreatePostCommand,它封装了创建博客文章所需的数据:

namespace CQRSExample.Application.Commands
{public class CreatePostCommand{public string Title { get; }public string Content { get; }public CreatePostCommand(string title, string content){Title = title;Content = content;}}
}

命令处理器 CreatePostCommandHandler 负责处理这个命令,并将其持久化到数据库:

using System.Threading.Tasks;
using CQRSExample.Application.Interfaces;
using CQRSExample.Domain;namespace CQRSExample.Application.Commands
{public class CreatePostCommandHandler{private readonly IPostRepository _postRepository;public CreatePostCommandHandler(IPostRepository postRepository){_postRepository = postRepository;}public async Task Handle(CreatePostCommand command){var post = new Post(command.Title, command.Content);await _postRepository.AddAsync(post);}}
}
3.2.2 查询部分(Query)

查询模型 GetPostQueryGetAllPostsQuery 分别表示查询单个文章和所有文章的请求:

namespace CQRSExample.Application.Queries
{public class GetPostQuery{public int Id { get; }public GetPostQuery(int id){Id = id;}}public class GetAllPostsQuery { }
}

查询处理器分别处理获取单个文章和所有文章的请求:

using System.Threading.Tasks;
using CQRSExample.Application.Interfaces;
using CQRSExample.Domain;namespace CQRSExample.Application.Queries
{public class GetPostQueryHandler{private readonly IPostRepository _postRepository;public GetPostQueryHandler(IPostRepository postRepository){_postRepository = postRepository;}public async Task<Post> Handle(GetPostQuery query){return await _postRepository.GetByIdAsync(query.Id);}}public class GetAllPostsQueryHandler{private readonly IPostRepository _postRepository;public GetAllPostsQueryHandler(IPostRepository postRepository){_postRepository = postRepository;}public async Task<List<Post>> Handle(GetAllPostsQuery query){return await _postRepository.GetAllAsync();}}
}
3.2.3 基础设施层(Infrastructure)

PostRepository 类实现了 IPostRepository 接口,负责从数据源(在本例中是内存)获取数据:

using System.Collections.Generic;
using System.Threading.Tasks;
using CQRSExample.Application.Interfaces;
using CQRSExample.Domain;namespace CQRSExample.Infrastructure
{public class PostRepository : IPostRepository{private static readonly List<Post> Posts = new List<Post>();public async Task AddAsync(Post post){Posts.Add(post);await Task.CompletedTask;}public async Task<Post> GetByIdAsync(int id)

{ return await Task.FromResult(Posts.Find(p => p.Id == id)); }

    public async Task<List<Post>> GetAllAsync(){return await Task.FromResult(Posts);}
}

}


##### 3.2.4 **Web 层(Controller)**`PostsController` 处理 HTTP 请求,并将请求分派到相应的命令和查询处理器:```csharp
using Microsoft.AspNetCore.Mvc;
using CQRSExample.Application.Commands;
using CQRSExample.Application.Queries;namespace CQRSExample.Web.Controllers
{[ApiController][Route("api/[controller]")]public class PostsController : ControllerBase{private readonly CreatePostCommandHandler _createPostHandler;private readonly GetPostQueryHandler _getPostHandler;private readonly GetAllPostsQueryHandler _getAllPostsHandler;public PostsController(CreatePostCommandHandler createPostHandler,GetPostQueryHandler getPostHandler,GetAllPostsQueryHandler getAllPostsHandler){_createPostHandler = createPostHandler;_getPostHandler = getPostHandler;_getAllPostsHandler = getAllPostsHandler;}[HttpPost]public async Task<IActionResult> Create([FromBody] CreatePostCommand command){await _createPostHandler.Handle(command);return Ok();}[HttpGet("{id}")]public async Task<IActionResult> Get(int id){var post = await _getPostHandler.Handle(new GetPostQuery(id));return Ok(post);}[HttpGet]public async Task<IActionResult> GetAll(){var posts = await _getAllPostsHandler.Handle(new GetAllPostsQuery());return Ok(posts);}}
}

四、总结

通过以上代码实现,我们展示了如何在 .NET Core 中使用 命令查询职责分离(CQRS)模式来优化系统的读写操作。通过将命令(写操作)和查询(读操作)分离,我们实现了:

  1. 独立的命令和查询模型,提升了灵活性和扩展性。
  2. 清晰的职责分离,确保代码更易于维护和测试。
  3. 性能优化,为读和写分别使用独立的处理方式,可以根据需求进行不同的优化。

CQRS 是一种强大的架构模式,特别适用于高并发、大规模系统和复杂的业务场景。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/889391.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

读DAMA数据管理知识体系指南04数据治理(上)

读DAMA数据管理知识体系指南04数据治理(上)1. 数据治理 1.1. Data Governance, DG1.1.1. 定义是在管理数据资产过程中行使权力和管控,包括计划、监控和实施1.1.2. 数据治理职能是指导所有其他数据管理领域的活1.1.3. 目的是确保根据数据管理制度和最佳实践正确地管理数据1.1…

Virtual Box设置桥接模式

一、硬件 1.查看宿主机ip cmd命令:ipconfig 无线局域网适配器 WLAN:连接特定的 DNS 后缀 . . . . . . . :IPv6 地址 . . . . . . . . . . . . : 保密本地链接 IPv6 地址. . . . . . . . : fe80::f087:44c8:88bc:b041%11IPv4 地址 . . . . . . . . . . . . : 192.168.43.149子网…

【NuGet攻略】C#开发者必备技能:构建发布开发工具包

来源 https://mp.weixin.qq.com/s/YqKTdyLXuODBLxnhar52Xg 2025年02月22日 17:44 广东前言 在 C# 开发领域里,构建代码库,分享与使用可复用代码的机制是十分必要的,它可以提高开发效率。通常,我们把可复用代码打包后的文件称为包(Package)。 在 .NET 中,通过使用 NuGet…

【2025穿越时空 怀旧经典】VMware Workstation Pro助你重返WindowsXP,让怀旧不再是空谈!

🚀 个人主页 极客小俊 ✍🏻 作者简介:web开发者、设计师、技术分享 🐋 希望大家多多支持, 我们一起学习和进步! 🏅 欢迎评论 ❤️点赞💬评论 📂收藏 📂加关注使用VMware Workstation Pro 虚拟机安装XP系统 让你瞬间回到黄金年代准备WindowsXP ISO镜像文件 大家…

【2025最新】VMware Workstation Pro 虚拟机配置与安装VMware Tools 感受它的强大~

🚀 个人主页 极客小俊 ✍🏻 作者简介:web开发者、设计师、技术分享 🐋 希望大家多多支持, 我们一起学习和进步! 🏅 欢迎评论 ❤️点赞💬评论 📂收藏 📂加关注安装VMware Tools VMware Tools是什么 VMware Tools是VMware虚拟机中自带的一种增强工具,它提供了一…

【2025最新】 神奇!VMware Workstation Pro虚拟机还原与删除功能,让你轻松应对各种场景!

🚀 个人主页 极客小俊 ✍🏻 作者简介:web开发者、设计师、技术分享 🐋 希望大家多多支持, 我们一起学习和进步! 🏅 欢迎评论 ❤️点赞💬评论 📂收藏 📂加关注删除虚拟机 删除虚拟机其实很简单,首先选择你要删除的虚拟机, 然后鼠标右键, 选择移除即可! 如图但这…

VMware Workstation Pro 虚拟机基础配置教程,方便你的神操作!

🚀 个人主页 极客小俊 ✍🏻 作者简介:web开发者、设计师、技术分享 🐋 希望大家多多支持, 我们一起学习和进步! 🏅 欢迎评论 ❤️点赞💬评论 📂收藏 📂加关注VMware Workstation Pro 基本配置与操作 在我们安装好VMware之后,接下来我们可以对VMware做一个基本的…

web开发 辅助学习管理系统开发日记 day1

现在时间:2025/2/25/2:52 在这个寒假与很多人交流之后我决心从算法竞赛转开发了,比预计的时间要早一点不过也无伤大雅。可能会在博客上分享一些自己开发中遇到的一些问题和知识。 本来一直在着手的苍穹外卖的开发,但由于知识上存在各种漏洞,以及自己的能力问题,决心先从简单…

2 本地部署DeepSeek模型构建本地知识库+联网搜索详细步骤

在1 使用ollama完成DeepSeek本地部署中使用ollama完成deepSeek的本地部署和运行,此时我可以在PowerShell中通过对话的方式与DeepSeek交流,但此时本地模型不具备联网搜索能力,无法根据网上信息来回答我的问题,同时我也无法将我自己的知识给他参考,这样本地模型相比直接使用…

【毕业设计】【河蟹识别微信小程序】项目总结

一、项目需求与背景 1. 项目背景与需求近年来,相关水产公司以及水产研究所等,利用类似水池生态育种等群体育种方法, 共培育了“长江 1 号”、“长江 2 号”、 “光合 1 号”、“诺亚 1 号”、“江海 21 号”以及“长荡湖 1 号”总计 6 类优良品种。这些新培育的河蟹品种,不仅…

日志(logging)

【一】函数式简单配置 import logging logging.debug(debug message) logging.info(info message) logging.warning(warning message) logging.error(error message) logging.critical(critical message)默认情况下Python的logging模块将日志打印到了标准输出中,且只…

解决Docker连接XQuartz报错

背景 打开XQuartz,在桌面右上角菜单栏中的XQuartz偏好设置中的安全性中启用 "允许从网络客户端连接",并重启XQuartz生效,后续操作中要保持XQuartz为打开状态,不要退出XQuartz报错内容: root@9b90039a4d17:~# ros2 run turtlesim turtlesim_node Authorization r…