CQRS 模式 - 处理命令时需要读取数据吗?

CQRS pattern - need to read data when processing a command?

我正在练习CQRS模式,我看不懂。我需要执行一条命令来创建一个实体,该实体又具有导航属性。原来在创建的时候,我是通过ObjectId向数据库请求数据的。但事实证明我在命令中进行查询。

public async Task<ResponseBase> Handle(CommandCreateItem request, CancellationToken cancellationToken)
{
    var dto = request.Item;
    var newItem = new Item();

    _color = await _context.Colors.FindAsync(dto.ColorId);
    _seasonItem = await _context.SeasonItems.FindAsync(dto.SeasonItemId);
    _itemType = await _context.ItemTypes.FindAsync(dto.ItemTypeId);


    var price = decimal.Parse(dto.Price, NumberStyles.Any, CultureInfo.InvariantCulture);
    var countItem = uint.Parse(dto.CountItem);

    var characteristic = new CharacteristicItem
    {
        Color = _color, SeasonItem = _seasonItem,ItemType = _itemType, Id = Guid.NewGuid(),Item = newItem
    };
    newItem = new Item
    {
        Id = Guid.NewGuid(),
        Title = dto.Title,
        ArticleNumber = dto.ArticleNumber,
        Description = dto.Description,
        NumberOfSales = 0,
        CountItem = countItem,
        Price = price,
        CharacteristicItem = characteristic,
    };
    await _context.CharacteristicItems.AddAsync(characteristic, cancellationToken);
    await _context.Items.AddAsync(newItem, cancellationToken);
    await _context.SaveChangesAsync(cancellationToken);

    return new ResponseItemCreate(newItem.Id);
}

这正常吗?怎么做才对?毕竟它的本质是分担责任。

当CQRS的概念定义写操作命令和读操作查询的隔离时,并不意味着它必须像你想象的那么严格。

责任分离是有原因的。 例如,其中之一是根据业务需求从读取操作中扩展写入操作。 其他,就是根据需要使用不同的数据库。

您可以找到关于此的更多信息msdn documentation

TL;DR 当您执行命令 (C) 例如无状态设计中的事务。

它是 CQRS 的读取 (Q) 端,需要您找到执行查询和 read activity from alternate data sources 的新颖方法,以释放真实来源的容量。

例如在OP的例子中,如果colorseasonItemitemType等外键数据不经常变化,可以考虑缓存在内存或分布式缓存中。此外,在 Entity Framework 中,您应该能够 associate navigation properties via foreign key ids(而不是获取完整的跟踪实体),这可以避免完全获取 FK 对象的需要,因为 ID 已经在传入请求中可用。

详情

简单地说,CQRS 意味着读取数据不需要从同一个数据源完成,也不需要以与写入相同的方式完成。

通过坚持这一原则,CQRS 允许具有更多读取 activity 的系统(例如 UI 视图、GET 查询 API 等)扩展到远远超过从中读取和写入的等效系统集中式数据存储(真相来源)本来是允许的,例如来自单个 SQL RDBMS。

由于大多数 scaled-out 事务 (OLTP) 系统是无状态的,它们在持久数据存储中维护状态,因此大多数系统将需要读取当前状态(真实)以断言任何先决条件和规则或在应用任何更改之前进行验证。

因此,您的无状态 'read before write' 处理命令事务的方法没有任何问题。无论如何,CAP theorem 在保持数据状态完整性的同时限制了写入事务的扩展选项。

无状态事务系统通常需要使用悲观锁定或乐观并发模式,以确保基于读取数据所做假设的任何事务在 'read' 和 'write' activity。使用悲观锁定,您将需要从真正的源读取和写入。通过乐观并发,您还可以将命令基于 CQRS 读取存储,但您需要通过数据版本或时间戳跟踪并断言在此期间没有任何更改。

CQRS 中单独读取存储的主要好处是 non-transactional 读取 activity,例如UI、报告、分析等不需要绝对新鲜数据的点读取 (GetById) 或查询。如果可以从更快的 'cache'(通常是异步分布、复制和更新,因此 eventually consistent)完成读取,那么系统的整体规模就会提高。这些读取缓存(通常称为读取端或读取存储)允许 read-heavy 系统扩展到超出共享 read-write 数据库的限制。

在处理依赖于实体/聚合根的现有状态的命令时,避免无状态 read-before 写入 activity 的唯一方法同时仍然保持源的一致性和完整性数据库,是将真正的源从存储中更改到内存中,例如Actor Model的具体实现。然而,转向有状态设计会面临不同的挑战,例如路由(用于扩展)和故障转移/容错。

因此,对于 read-scalable、无状态 CQRS 系统,请确保您的命令写入高效且一致,但将大部分精力花在寻找新的方法来服务于从缓存中读取和查询 activity(例如in-memory、分布式、预计算投影等)。响应式或 Event-Driven 架构是帮助实现此结果的一种方法。