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的例子中,如果color
、seasonItem
、itemType
等外键数据不经常变化,可以考虑缓存在内存或分布式缓存中。此外,在 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 架构是帮助实现此结果的一种方法。
我正在练习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的例子中,如果color
、seasonItem
、itemType
等外键数据不经常变化,可以考虑缓存在内存或分布式缓存中。此外,在 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 架构是帮助实现此结果的一种方法。