











https://jdaniel1987.github.io/CQRS#link-to-example-project
https://github.com/fanqingsong/GameShop.CQRS
这是一篇基于你提供的网页内容整理的深度技术博客文章。为了便于读者理解,我重新梳理了结构,并保留了原文中的架构图示和代码示例。
CQRS (Command Query Responsibility Segregation,命令查询职责分离) 是一种架构模式,它将应用程序中的“读取”(Query)和“写入”(Command)操作分离开来。
这种分离可以简化设计,提高性能和可扩展性,并有助于在复杂系统中进行数据管理。在这篇文章中,我们将深入探讨如何在 .NET 项目中实施 CQRS。
CQRS 模式将系统的逻辑拆分为两个截然不同的部分:
这种分离允许我们独立优化每一项职责,这对于高性能应用或管理复杂数据的系统特别有用。

CQRS 的基本流向是将读写路径物理或逻辑上分开:
架构流向:
Client (客户端) ➡️ Write API (写API) ➡️ Write DB (写数据库)
⬇️ (同步 SYNC)
Read DB (读数据库) ⬅️ Read API (读API) ⬅️ Client (客户端)
CQRS 在用户体验和性能至关重要的场景中非常有益。以下是实施 CQRS 的关键场景:

💡 专家提示:
将项目拆分为独立的读写组件并非强制性的;你可以将它们组织在同一项目的不同文件夹中。但是,为了在 Kubernetes 上部署时利用可扩展性优势,我强烈建议将它们分开。
同样,拥有独立的读写数据库也不是绝对必须的。不过,针对读取优化的数据库(如只读副本)可以独立扩展以满足需求。
架构流向:
Write API ➡️ MongoDB (Primary)
⬇️ (Replication)
MongoDB (Replica Set) ➡️ Read API
MongoDB (Replica Set) ➡️ Read API
我们将结合 Clean Architecture (整洁架构) 来演示。以下是推荐的解决方案结构:
Solution 'GameShop'
├── src
│ ├── Application
│ │ ├── GameShop.Application.Read (读应用层)
│ │ └── GameShop.Application.Write (写应用层)
│ ├── Domain (领域层 - 共享)
│ ├── Infrastructure
│ │ ├── GameShop.Infrastructure.Data
│ │ ├── GameShop.Infrastructure.Read (读基础设施)
│ │ └── GameShop.Infrastructure.Write(写基础设施)
│ └── Presentation
│ ├── GameShop.API.Read (读接口)
│ └── GameShop.API.Write (写接口)
我们将定义一个 API 端点来检索名称包含指定字符串的所有游戏信息。
使用 Minimal APIs 和 MediatR。
public class GetGamesByNameConsolesModule : ICarterModule
{
public void AddRoutes(IEndpointRouteBuilder app)
{
app.MapGet("api/GamesByName/{GameName}", async (IMediator mediator, string gameName) =>
{
// 1. 创建查询对象
var query = new GetGamesByNameQuery(gameName);
// 2. 通过 MediatR 发送查询
var result = await mediator.Send(query);
// 3. 返回结果
return result.IsSuccess
? Results.Ok(result.Value.ToGetGamesByNameResponse())
: Results.BadRequest(result.Error);
});
}
}
核心逻辑位于 Handler 中。
public class GetGamesByNameHandler(IGameReadRepository gameReadRepository)
: IRequestHandler<GetGamesByNameQuery, IResult<GetGamesByNameQueryResponse>>
{
private readonly IGameReadRepository _gameReadRepository = gameReadRepository;
public async Task<IResult<GetGamesByNameQueryResponse>> Handle(GetGamesByNameQuery query, CancellationToken cancellationToken)
{
// 从基础设施层获取数据
var games = await _gameReadRepository.GetGamesByName(query.GameName, cancellationToken);
// 应用业务规则(如果有)...
return Result.Success(games.ToGetGamesByNameQueryResponse());
}
}
数据模型 (使用 Record 实现不可变性):
public record GetGamesByNameQuery(string GameName) : IRequest<IResult<GetGamesByNameQueryResponse>>;
public record GetGamesByNameQueryResponse(IReadOnlyCollection<GetGamesByNameQueryResponseItem> Games);
public record GetGamesByNameQueryResponseItem(
int Id, string Name, string Publisher, double Price, int GameConsoleId, string GameConsoleName);
只读存储库实现。
public class GameReadRepository(IDbContextFactory<ReadOnlyDatabaseContext> readOnlyDatabaseContextFactory)
: IGameReadRepository
{
private readonly IDbContextFactory<ReadOnlyDatabaseContext> _readOnlyDatabaseContextFactory = readOnlyDatabaseContextFactory;
public async Task<IReadOnlyCollection<Game>> GetGamesByName(string gameName, CancellationToken cancellationToken)
{
var readOnlyDbContext = await _readOnlyDatabaseContextFactory.CreateDbContextAsync(cancellationToken);
return await readOnlyDbContext
.Games
.Include(g => g.GameConsole)
.Where(g => EF.Functions.Like(g.Name, $"%{gameName}%"))
.ToArrayAsync(cancellationToken);
}
}
我们将定义一个 API 端点向系统添加新的游戏主机 (Game Console)。
public class AddGameConsoleModule : ICarterModule
{
public void AddRoutes(IEndpointRouteBuilder app)
{
app.MapPost("api/AddGameConsole", async (IMediator mediator, AddGameConsoleRequest request, CancellationToken cancellationToken) =>
{
// 1. 转换为命令对象
var command = request.ToCommand();
// 2. 发送命令
var result = await mediator.Send(command, cancellationToken);
// 3. 返回结果
return result.IsSuccess
? Results.Created()
: Results.BadRequest(result.Error);
});
}
}
public class AddGameConsoleHandler(IGameConsoleWriteRepository gameConsoleWriteRepository)
: IRequestHandler<AddGameConsoleCommand, IResult<AddGameConsoleCommandResponse>>
{
private readonly IGameConsoleWriteRepository _gameConsoleWriteRepository = gameConsoleWriteRepository;
public async Task<IResult<AddGameConsoleCommandResponse>> Handle(AddGameConsoleCommand command, CancellationToken cancellationToken)
{
// 转换为领域对象
var gameConsole = command.ToDomain();
// 保存到基础设施层
await _gameConsoleWriteRepository.AddGameConsole(gameConsole, cancellationToken);
return Result.Success(gameConsole.ToAddGameConsoleCommandResponse());
}
}
数据模型:
public record AddGameConsoleCommand(string Name, string Manufacturer, double Price)
: IRequest<IResult<AddGameConsoleCommandResponse>>;
public record AddGameConsoleCommandResponse(int Id, string Name);
写入存储库实现。
public class GameConsoleWriteRepository(IDbContextFactory<WriteReadDatabaseContext> writeReadDbContextFactory)
: IGameConsoleWriteRepository
{
private readonly IDbContextFactory<WriteReadDatabaseContext> _writeReadDbContextFactory = writeReadDbContextFactory;
public async Task AddGameConsole(GameConsole gameConsole, CancellationToken cancellationToken)
{
var writeReadDbContext = await _writeReadDbContextFactory.CreateDbContextAsync(cancellationToken);
await writeReadDbContext.AddAsync(gameConsole, cancellationToken);
await writeReadDbContext.SaveChangesAsync(cancellationToken);
}
}
CQRS 是一个强大的模式,通过分离与读写数据相关的关注点来简化复杂系统。在 .NET 中采用此模式,可以显著提高应用程序的可扩展性、灵活性和可维护性。
希望这篇指南能帮助你更好地理解并在项目中应用 CQRS!
出处:http://www.cnblogs.com/lightsong/ 本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接。
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。