96SEO 2026-05-06 12:55 20
说实话,咱们Zuo后端开发的,谁没写过那种几千行的“上帝类”?打开一个 OrderService,里面又是查库、又是校验、又是发消息,简直像个大杂烩。一开始觉得挺顺手,毕竟增删改查嘛,不就那点事儿?但随着业务像滚雪球一样越滚越大,你会发现这个类越来越难维护,改一处bug,生怕牵一发而动全身。

这时候,hen多人会开始焦虑:是不是架构没设计好?是不是要上微服务?其实别想太复杂。hen多时候,我们只是把“读”和“写”这两件本质完全不同的事情,强行塞进了同一个模具里。今天咱们就来聊聊,如何通过 CQRS把这一团乱麻理顺,别再把简单的增删改查混为一谈了。
我们是如何一步步走进“大泥球”的?先来kankan大家Zui熟悉的场景。在项目初期,为了追求速度,我们通常会写出类似下面这样的代码。这没什么丢人的,这是进化的必经之路。
public class OrderService
{
public async Task CreateAsync { ... }
public async Task CancelAsync { ... }
public async Task GetByIdAsync { ... }
public async Task GetListAsync { ... }
public async Task GetSalesAmountAsync { ... }
}
乍一kan,这代码整洁得像教科书。一个类管完了订单的所有生老病死。但你要知道,这种“kan起来没问题”的错觉,往往是噩梦的开始。
为什么?因为这几行代码背后的意图完全不同。创建订单和取消订单是写操作,它们要改变系统的状态,要保证数据一致性,甚至要处理复杂的业务规则。而获取列表和统计销售额是读操作,它们的目标是尽可Neng快、尽可Neng灵活地把数据吐出来给前端展示。
当你把这两者混在一起时尴尬的事情就来了。比如为了优化列表页的查询速度,你想加个索引,或者不想加载某些关联表,但因为这个 DbContext 同时也被写逻辑占用,你不得不小心翼翼,生怕改了什么配置导致订单保存失败。这就是典型的“由于职责不清导致的相互牵制”。
一提到 CQRS,hen多同行的脑海里立马会浮现出双数据库、Zui终一致性、事件溯源这些听起来就hen“贵”的架构词汇。其实这dou是被吓唬出来的。
CQRS 的全称是 Command Query Responsibility Segregation。翻译成人话,就是:把“改状态”和“查状态”这两件事分开处理。它 是一种代码组织和职责拆分的手段,至于要不要分库分表,那是后面演化的事,不是入门的门槛。
咱们Ke以把 CQRS 想成一条hen直白的分界线。左边是命令,右边是查询。别让左边的人操心右边的数据怎么展示,也别让右边的人操心左边的数据怎么校验。
命令:只管“Zuo”,别问“结果”先kan写操作。在 CQRS 的语境下我们不再直接调用 Service 的方法,而是定义一个“命令”。命令代表着一种意图,比如“创建订单”或者“取消订单”。
这里有个hen重要的原则:命令处理器不应该返回一大堆展示数据。它的任务是执行业务逻辑,修改数据库状态,然后返回一个简单的标识或者干脆无返回。真正需要避免的是:命令处理器顺手查一大坨展示数据再返回。那不是它该干的事。
咱们先定义一个简单的命令对象,它就是个纯粹的数据载体:
using MediatR;
public sealed record CreateOrderCommand(
int CustomerId,
decimal TotalAmount
) : IRequest;
然后我们写一个专门处理这个命令的 Handler。注意kan,这里的逻辑非常纯粹:
using MediatR;
public sealed class CreateOrderCommandHandler
: IRequestHandler
{
private readonly AppDbContext _db;
public CreateOrderCommandHandler
{
_db = db;
}
public async Task Handle(
CreateOrderCommand request,
CancellationToken cancellationToken)
{
if
{
throw new ArgumentException;
}
var order = new Order
{
OrderNo = $"ORD{DateTime.UtcNow:yyyyMMddHHmmssfff}",
CustomerId = request.CustomerId,
TotalAmount = request.TotalAmount,
Status = OrderStatus.Pending,
CreatedAt = DateTime.UtcNow
};
_db.Orders.Add;
await _db.SaveChangesAsync;
return order.Id;
}
}
kan到 CreateOrderCommandHandler,你就知道它是改数据的。它的重点是“Zuo一件会产生副作用的事”。严格说命令geng关注副作用,而不是回传查询数据。虽然工程上返回一些必要信息完全正常,但千万别让它变成“查数据”的接口。
再kan查询。查询端的目标只有一个:以Zui快的速度、Zui贴合UI的格式把数据拿出来。它不应该修改任何状态,也不应该触发复杂的领域逻辑。
这时候,我们通常会定义专门的 Query 对象和 Handler。为了性Neng,查询端往往会直接返回 DTO,而不是那个沉重的领域实体。
public sealed record GetOrderDetailQuery;
public sealed class GetOrderDetailQueryHandler
: IQueryHandler
{
private readonly AppDbContext _db;
public GetOrderDetailQueryHandler
{
_db = db;
}
public async Task HandleAsync(
GetOrderDetailQuery query,
CancellationToken cancellationToken)
{
return await _db.Orders
.AsNoTracking // 关键:只读,不跟踪变geng
.Where
.Select(x => new OrderDetailDto
{
Id = x.Id,
OrderNo = x.OrderNo,
CustomerId = x.CustomerId,
TotalAmount = x.TotalAmount,
StatusText = x.Status.ToString,
CreatedAt = x.CreatedAt
})
.FirstOrDefaultAsync;
}
}
这段代码hen好地体现了 CQRS 里读模型的特点。它的重点是“把数据拿出来”,而不是“顺手geng新点什么”。Neng直接 Select 到 DTO,就别先整实体再映射一遍,那样纯属浪费资源。
查询处理器和命令处理器的味道明显不一样。前者像是在图书馆查阅资料,轻装上阵;后者像是在车间加工零件,严丝合缝。
代码结构的重塑:从 Service 到 Handler拆分是为了清晰,不是为了表演架构。当我们把读写拆开后Controller 层也会变得异常清爽。这时候通常会引入 MediatR Zuo请求分发,它就像一个不知疲倦的快递员,把请求扔给对应的 Handler。
public class OrdersController : ControllerBase
{
private readonly IMediator _mediator;
public OrdersController
{
_mediator = mediator;
}
public async Task Create(
CreateOrderCommand command,
CancellationToken cancellationToken)
{
int orderId = await _mediator.Send;
return Ok;
}
public async Task GetDetail
{
var result = await _mediator.Send, cancellationToken);
return result is null ? NotFound : Ok;
}
}
这套写法的好处是显而易见的:控制器变得hen薄,它甚至不知道业务逻辑是怎么跑的,它只负责收发请求。每个请求对象只表达一件事,每个处理器也只Zuo一件事。这种“按功Neng切片”的写法,在 CQRS 里通常geng顺手。
在 .NET 里除了按 Commands/Queries 文件夹分类,还有一种geng流行的按功Neng切片的组织方式:
Features
└── Orders
├── CreateOrder
│ ├── CreateOrderCommand.cs
│ └── CreateOrderCommandHandler.cs
├── CancelOrder
│ ├── CancelOrderCommand.cs
│ └── CancelOrderCommandHandler.cs
├── GetOrderDetail
│ ├── GetOrderDetailQuery.cs
│ ├── GetOrderDetailQueryHandler.cs
│ └── OrderDetailDto.cs
└── GetOrderList
├── GetOrderListQuery.cs
├── GetOrderListQueryHandler.cs
└── OrderListItemDto.cs
这种方式一开始hen好理解,但功Neng多了以后跳文件会比较频繁。不过因为业务意图越明确,代码越不容易拧巴,所以hen多团队geng喜欢这种结构。
领域逻辑的回归:别让实体变成贫血模型拆分了读写,并不意味着我们Ke以随便写代码。恰恰相反,CQRS 往往伴随着领域驱动设计的思想。
比如取消订单,在传统的 CRUD 写法里我们可Neng会在 Service 里直接写:
order.Status = OrderStatus.Cancelled;
这其实hen危险。Ru果订单Yi经支付了怎么办?Ru果Yi经发货了怎么办?这些规则Ru果散落在 Service 的各个角落,迟早有一天会出乱子。
而应该通过领域方法统一约束规则。kankan这个 Order 实体:
public class Order
{
// ... 省略其他属性
public void Cancel
{
if
{
throw new InvalidOperationException;
}
if
{
throw new InvalidOperationException;
}
Status = OrderStatus.Cancelled;
CancelledAt = DateTime.UtcNow;
}
}
这时候,取消订单的命令处理器就变得非常简单且富有语义:
public sealed class CancelOrderCommandHandler
: ICommandHandler
{
private readonly AppDbContext _db;
public CancelOrderCommandHandler
{
_db = db;
}
public async Task HandleAsync(
CancelOrderCommand command,
CancellationToken cancellationToken)
{
var order = await _db.Orders
.FirstOrDefaultAsync;
if
{
throw new KeyNotFoundException;
}
// 核心业务逻辑在实体内部
order.Cancel;
await _db.SaveChangesAsync;
}
}
这里的重点不是“改字段”,而是“执行业务意图”。这会比把一堆规则散落在控制器和服务里geng稳。
什么时候该踩刹车?说了这么多 CQRS 的好话,但必须得泼盆冷水:不是所有按钮点击dou值得拆成十几个类。
CQRS 是有成本的。你要定义geng多的类,写geng多的接口,维护geng多的文件。Ru果业务复杂度还没到那个程度,强上只会让简单问题复杂化。尤其是下面这些情况:
项目初期,模型还在剧烈变动。
就是一个简单的后台管理增删改查,读写压力极低。
团队规模hen小,大家dou在一个文件里改代码效率geng高。
hen多项目的合理路径其实是:前期用简单的 CRUD,随着业务增长,发现读写模型开始打架,这时候再引入 CQRS。这是geng稳的演进路线。
Ru果订单写入压力和查询压力差异hen大,架构可Neng会继续演化。比如引入消息队列,实现Zui终一致性,甚至读写分库。但那是“完整形态的 CQRS”,不是起步门槛。
别再把增删改查混为一谈,本质上就是:尊重读写的差异。
Command 是命令,负责改变系统状态;Query 是查询,负责读取数据,但不修改系统状态。这两者目标完全不一样,就像吃饭和睡觉,虽然dou是生活必需,但你不会在同一个房间里用同一套姿势完成它们。
通过 CQRS,我们把代码拆成了两半:一边严谨地处理业务规则,保证数据安全;另一边灵活地响应前端需求,追求极致性Neng。这样一拆,就Nengkan出差别了。代码阅读成本会明显下降,维护起来也不再像是在拆炸弹。
所以下次当你kan着那个臃肿的 Service 类发愁时不妨试着画一条线。把“写”的放左边,把“读”的放右边。相信我,你的代码会清爽hen多。
作为专业的SEO优化服务提供商,我们致力于通过科学、系统的搜索引擎优化策略,帮助企业在百度、Google等搜索引擎中获得更高的排名和流量。我们的服务涵盖网站结构优化、内容优化、技术SEO和链接建设等多个维度。
| 服务项目 | 基础套餐 | 标准套餐 | 高级定制 |
|---|---|---|---|
| 关键词优化数量 | 10-20个核心词 | 30-50个核心词+长尾词 | 80-150个全方位覆盖 |
| 内容优化 | 基础页面优化 | 全站内容优化+每月5篇原创 | 个性化内容策略+每月15篇原创 |
| 技术SEO | 基本技术检查 | 全面技术优化+移动适配 | 深度技术重构+性能优化 |
| 外链建设 | 每月5-10条 | 每月20-30条高质量外链 | 每月50+条多渠道外链 |
| 数据报告 | 月度基础报告 | 双周详细报告+分析 | 每周深度报告+策略调整 |
| 效果保障 | 3-6个月见效 | 2-4个月见效 | 1-3个月快速见效 |
我们的SEO优化服务遵循科学严谨的流程,确保每一步都基于数据分析和行业最佳实践:
全面检测网站技术问题、内容质量、竞争对手情况,制定个性化优化方案。
基于用户搜索意图和商业目标,制定全面的关键词矩阵和布局策略。
解决网站技术问题,优化网站结构,提升页面速度和移动端体验。
创作高质量原创内容,优化现有页面,建立内容更新机制。
获取高质量外部链接,建立品牌在线影响力,提升网站权威度。
持续监控排名、流量和转化数据,根据效果调整优化策略。
基于我们服务的客户数据统计,平均优化效果如下:
我们坚信,真正的SEO优化不仅仅是追求排名,而是通过提供优质内容、优化用户体验、建立网站权威,最终实现可持续的业务增长。我们的目标是与客户建立长期合作关系,共同成长。
Demand feedback