您好,登录后才能下订单哦!
# 怎么写出简洁的CQRS代码
## 前言
CQRS(Command Query Responsibility Segregation)是一种将读写操作分离的架构模式。通过将命令(写操作)和查询(读操作)分离到不同的模型中,可以提高系统的可扩展性、性能和安全性。然而,实现CQRS时容易陷入过度设计的陷阱,导致代码复杂度陡增。本文将探讨如何用简洁的方式实现CQRS模式。
## 一、理解CQRS的核心思想
### 1.1 CQRS的基本概念
CQRS的核心是将系统分为两个部分:
- **命令侧(Command)**:处理创建、更新和删除操作(CUD)
- **查询侧(Query)**:处理数据读取操作(R)
### 1.2 何时使用CQRS
适合场景:
- 读写负载差异大的系统
- 需要不同数据模型的场景
- 需要优化查询性能的场景
不适合场景:
- 简单CRUD应用
- 读写模型基本一致的系统
## 二、简洁实现CQRS的关键原则
### 2.1 保持简单
```csharp
// 不好的例子:过度抽象的命令处理器
public interface ICommandHandler<TCommand> where TCommand : ICommand
{
Task HandleAsync(TCommand command);
}
// 好的例子:简单的命令处理
public class CreateOrderCommand
{
public string ProductId { get; set; }
public int Quantity { get; set; }
}
public class OrderService
{
public async Task Handle(CreateOrderCommand command)
{
// 直接的处理逻辑
}
}
不要一开始就考虑事件溯源、复杂消息总线等高级概念。从简单的分离开始:
传统CRUD:
Controller -> Service -> Repository
简单CQRS:
CommandController -> CommandService -> CommandRepository
QueryController -> QueryService -> QueryRepository
从简单分离开始,根据需要逐步引入: 1. 先分离读写模型 2. 再考虑不同存储 3. 最后引入事件溯源等高级特性
// 好的命令设计示例
public class UpdateUserEmailCommand
{
public Guid UserId { get; }
public string NewEmail { get; }
public UpdateUserEmailCommand(Guid userId, string newEmail)
{
UserId = userId;
NewEmail = newEmail;
}
}
public class UpdateUserEmailCommandValidator : AbstractValidator<UpdateUserEmailCommand>
{
public UpdateUserEmailCommandValidator()
{
RuleFor(cmd => cmd.UserId).NotEmpty();
RuleFor(cmd => cmd.NewEmail).NotEmpty().EmailAddress();
}
}
public class UpdateUserEmailHandler
{
private readonly IUserRepository _repository;
public UpdateUserEmailHandler(IUserRepository repository)
{
_repository = repository;
}
public async Task Handle(UpdateUserEmailCommand command)
{
var user = await _repository.GetByIdAsync(command.UserId);
user.UpdateEmail(command.NewEmail);
await _repository.SaveAsync(user);
}
}
public class GetUserDetailsQuery
{
public Guid UserId { get; }
public GetUserDetailsQuery(Guid userId)
{
UserId = userId;
}
}
public class UserDetailsDto
{
public Guid Id { get; set; }
public string Name { get; set; }
public string Email { get; set; }
// 只包含视图需要的字段
}
public class GetUserDetailsHandler
{
private readonly IUserQueryRepository _queryRepository;
public async Task<UserDetailsDto> Handle(GetUserDetailsQuery query)
{
return await _queryRepository.GetUserDetailsAsync(query.UserId);
}
}
// 在查询存储库中直接返回DTO
public interface IUserQueryRepository
{
Task<UserDetailsDto> GetUserDetailsAsync(Guid userId);
}
// EF Core实现示例
public async Task<UserDetailsDto> GetUserDetailsAsync(Guid userId)
{
return await _context.Users
.Where(u => u.Id == userId)
.Select(u => new UserDetailsDto
{
Id = u.Id,
Name = u.Name,
Email = u.Email
})
.FirstOrDefaultAsync();
}
// 命令处理完成后同步更新读模型
public class UpdateUserEmailHandler
{
// ... 其他代码
public async Task Handle(UpdateUserEmailCommand command)
{
// 更新写模型
var user = await _writeRepository.GetByIdAsync(command.UserId);
user.UpdateEmail(command.NewEmail);
await _writeRepository.SaveAsync(user);
// 同步更新读模型
await _readRepository.UpdateEmailAsync(command.UserId, command.NewEmail);
}
}
// 定义领域事件
public class UserEmailUpdatedEvent
{
public Guid UserId { get; }
public string NewEmail { get; }
public UserEmailUpdatedEvent(Guid userId, string newEmail)
{
UserId = userId;
NewEmail = newEmail;
}
}
// 命令处理中发布事件
public async Task Handle(UpdateUserEmailCommand command)
{
// ... 更新逻辑
// 发布事件
await _eventPublisher.PublishAsync(
new UserEmailUpdatedEvent(command.UserId, command.NewEmail));
}
// 事件处理器更新读模型
public class UserEmailUpdatedEventHandler
{
private readonly IUserQueryRepository _readRepository;
public async Task Handle(UserEmailUpdatedEvent @event)
{
await _readRepository.UpdateEmailAsync(@event.UserId, @event.NewEmail);
}
}
[Test]
public async Task UpdateUserEmail_Should_UpdateEmailInWriteModel()
{
// 准备
var userId = Guid.NewGuid();
var originalEmail = "old@example.com";
var newEmail = "new@example.com";
var user = new User(userId, "test", originalEmail);
var mockRepo = new Mock<IUserRepository>();
mockRepo.Setup(r => r.GetByIdAsync(userId)).ReturnsAsync(user);
var handler = new UpdateUserEmailHandler(mockRepo.Object);
// 执行
await handler.Handle(new UpdateUserEmailCommand(userId, newEmail));
// 验证
user.Email.Should().Be(newEmail);
mockRepo.Verify(r => r.SaveAsync(user), Times.Once);
}
[Test]
public async Task GetUserDetails_Should_ReturnCorrectDto()
{
// 准备
var userId = Guid.NewGuid();
var expectedDto = new UserDetailsDto
{
Id = userId,
Name = "Test",
Email = "test@example.com"
};
var mockRepo = new Mock<IUserQueryRepository>();
mockRepo.Setup(r => r.GetUserDetailsAsync(userId))
.ReturnsAsync(expectedDto);
var handler = new GetUserDetailsHandler(mockRepo.Object);
// 执行
var result = await handler.Handle(new GetUserDetailsQuery(userId));
// 验证
result.Should().BeEquivalentTo(expectedDto);
}
问题表现: - 过早引入复杂事件总线 - 为每个命令/查询创建过多抽象层 - 在不必要的情况下使用事件溯源
解决方案: 从简单实现开始,随着需求演进逐步增加复杂性。
问题表现: - 读写模型不一致 - 同步延迟导致用户体验问题
解决方案: 1. 对于关键数据,采用同步更新 2. 对于非关键数据,接受最终一致性 3. 提供用户界面反馈机制
问题表现: - 读写模型中有重复的验证逻辑 - 相似的DTO定义
解决方案: 1. 共享验证逻辑(如FluentValidation规则) 2. 使用代码生成工具减少重复 3. 在适当层级共享简单DTO
当简单CQRS不能满足需求时,可以考虑:
// 事件溯源示例
public class User : EventSourcedAggregate
{
public string Name { get; private set; }
public string Email { get; private set; }
public void UpdateEmail(string newEmail)
{
Apply(new UserEmailUpdatedEvent(Id, newEmail));
}
protected override void When(object @event)
{
switch (@event)
{
case UserEmailUpdatedEvent e:
Email = e.NewEmail;
break;
}
}
}
// 使用MediatR实现进程内消息总线
public class UpdateUserEmailCommand : IRequest<Unit> { ... }
public class UpdateUserEmailHandler : IRequestHandler<UpdateUserEmailCommand, Unit>
{
public async Task<Unit> Handle(UpdateUserEmailCommand request, CancellationToken ct)
{
// 处理逻辑
return Unit.Value;
}
}
// 控制器调用
[HttpPut("email")]
public async Task<IActionResult> UpdateEmail([FromBody] UpdateUserEmailCommand command)
{
await _mediator.Send(command);
return Ok();
}
简洁的CQRS实现关键在于: 1. 从简单分离开始 2. 避免过早优化 3. 渐进式演进 4. 根据实际需求调整复杂度
记住,CQRS是一种架构模式,而不是目标本身。最适合你项目的实现,才是最好的实现。
本文共计约3700字,涵盖了从CQRS基础概念到简洁实现的全过程,并提供了代码示例和实用建议。希望对你实现简洁有效的CQRS架构有所帮助。 “`
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。