FindAndModify 在哪里适合 CQRS?

Where does FindAndModify fit into CQRS?

在我当前的应用程序中,我有以下 MongoDB FindAndModify 对 MongoDB 服务器的调用

public static TEntity PeekForRemoveSync<TEntity>(this MongoCollection<TEntity> collection, string reason)
    where TEntity : class, IEntity, IUpdatesTrackable, ISyncable, IDeletable, IApprovable
{
    if (reason == null)
    {
        throw new ArgumentNullException(nameof(reason));
    }

    IMongoQuery query = Query.And(
        Query<TEntity>.EQ(e => e.SyncInfo.IsDirty, true),
        Query<TEntity>.NE(e => e.Deletion, null),
        Query<TEntity>.NE(e => e.Approval, null),
        Query<TEntity>.EQ(e => e.SyncInfo.HumanInvestigateFlag, null),
        Query.Or(
            Query<TEntity>.EQ(e => e.SyncInfo.IsUpdateInProgress, false),
            Query<TEntity>.LT(e => e.SyncInfo.UpdateStartInfo.OccuredOn, DateTime.UtcNow.AddSeconds(-SyncConstants.PeekExpireTimeInSeconds))
        ),
        Query<TEntity>.LTE(e => e.SyncInfo.PeekedCount, MaxPeekedCount)
    );

    return PeekForSync(collection, query, reason);
}

private static TEntity PeekForSync<TEntity>(this MongoCollection<TEntity> collection, IMongoQuery query, string reason)
    where TEntity : class, IEntity, IUpdatesTrackable, ISyncable, IDeletable
{
    UpdateBuilder<TEntity> update = Update<TEntity>
        .Inc(e => e.SyncInfo.PeekedCount, 1)
        .Set(e => e.SyncInfo.UpdateStartInfo, new OccuranceWithReason(reason))
        .Set(e => e.SyncInfo.IsUpdateInProgress, true);

    SortByBuilder<TEntity> sort = SortBy<TEntity>.Descending(e => e.LastUpdatedOn);

    FindAndModifyArgs fmArgs = new FindAndModifyArgs
    {
        Query = query,
        Update = update,
        SortBy = sort,
        VersionReturned = FindAndModifyDocumentVersion.Modified
    };

    FindAndModifyResult result = collection.FindAndModify(fmArgs);

    return result.ModifiedDocument != null
        ? result.GetModifiedDocumentAs<TEntity>()
        : null;
}

Am I reinventing yet another queue system with MongoDB? That's not my intention here but it's what it's. Just ignore the business logic there.

在 CQRS 中,Query 和 Command 的含义如下(我相信):

命令: 更改系统状态,return 无值。

查询:不改变系统状态和return一些值。

如果是这样,我上面的 FindAndModify 调用(或任何其他与此查询类似的调用)适合哪里?

我不确定我是否答对了你的问题,所以这是我所理解的答案:

我喜欢将 CQRS 视为 并发模式 数据模型指南 的组合。

数据模型指南是比较明显的指南,毕竟您现在使用的模型不止一种。在非 CQRS 场景中,一个(未完成的)订单或购物车可能有以下对象:

{  items : [ { 'sku' : '235423', 'qty' : 3, 'price' : 4.34, ... }, ... ],
   orderStatus : 'in_cart',
   customerId : null,
   ...
}

现在,更新该模型本身很容易,但 CQRS 告诉您不要这样做。假设我想将产品“1234”添加到该购物车。当然,我可以 post 支持整个新购物车。但这既效率低下(我只需要信息 'i want to add 1 product 1234 to cart X'),又令人恼火,因为无论如何服务器都必须忽略总价。

然而,从本质上讲,我的 CQRS 主要是一个 并发方案 - 至少与 Event Sourcing. By keeping records of who changed what when, we can centralize the code that is responsible for applying the changes, for handling conflicts, allowing undo/redo and keeping an audit log, all at the same time. Of course, that comes at the expense of eventual consistency, but the notion is that an object that has many concurrent writers doesn't have a well-defined 'current' state anyway 相结合(至少我是这样解释的)。

此外,再看看那个购物车,也许更新模型毕竟不是微不足道的,因为购物车 可能 代表了对购物车中对象的锁定(至少对于冲突可能性很高的网站,例如限时抢购——如果产品在购物车中,我想知道我是否真的可以购买它,比如说 10 分钟)。没有命令对象,将不得不分析文档如何从状态 1 转换到状态 2 以及发生了什么变化,确切地说,不释放任何现有的锁,但可能获取一个新锁(或释放一个并获取一个新锁)。

相反,您可以发送 AddToCartCommand:

{ item : { 'sku' : '4242342', 'qty' : 4 },
  cartId : 53543 }

如果该产品售罄太快,也没有什么坏处,因为我们没有放弃购物车 - 添加到购物车命令失败,但购物车的状态保持完全不变。如果站点处于高负载状态并且买家又添加了另一种产品,则该命令可能会成功。如果可能的话,使用传统的 request/response 模式并不容易。

这可能不是最好的例子,但假设购物车同时被 多个 用户填充,那么简单的 'last write wins' 并发(无锁)或乐观看(错误:中间有人改文档)会出问题

在 CQRS 中,由于我们不进行即时更新,因此我们可以避开 RPC(一个 'method call' 告诉我立即发生了什么)和过于特定于客户端的接口。此外,与 RPC 与 REST 不同,CQRS 至少更深一层。虽然它肯定会对客户端代码产生很大影响,但它对服务器处理信息的方式具有巨大影响,以异步的、潜在的分布式方式甚至可能涉及完全不同的数据存储(因为无论如何模型都是不同的)。

无论如何,我认为您 post 编辑的代码可能是两个模型通信的地方,即必须应用更改的地方,这可能很棘手。一方面,这需要(分布式)事务,因此需要进行大量锁定并且锁定需要原子更新,因此在这种情况下 findAndModify 似乎是必需的。

我不确定偷看到底做了什么,但对我来说,您的代码看起来像是在尝试对文档进行短期锁定(根据 IsDirty : trueIsUpdateInProgress : false 标准) .

总而言之,我会说 CQRS 是网络的异步。

这将是自以为是,但这是我的看法:

CQRS 命令通常应该对单个聚合实体执行一些特定操作。

命令示例:

  • ChangePersonName
  • 下单
  • 删除帐户

如果在执行命令处理程序的过程中需要数据,它应该来自严格按其文档 ID 找到的文档。 (或 Mongo 中的 ObjectId

在一些文档数据库中,通过ID以外的东西进行查询是最终一致的操作。如果您依靠查询的结果来决定要采取什么行动,那么当索引过时时您可能会丢失一些项目。您如何知道该命令已针对所有数据真正完成?你不会。

如果查询结果确实完全一致(可能在 Mongo 中,我不是 100% 确定),即使那样你也有时间问题。您可能打算对与查询匹配的所有数据执行操作,但可能几毫秒后插入了与查询匹配的新数据。由于操作范围不受特定文档(或文档集)的限制,您真的无法知道您是否真的 完成

一般来说,我会说从 CQRS 命令中执行查询(除了文档 ID 之外的任何内容)不是一个好主意。

FindAndModify 貌似是用来打补丁的。您可能确实需要偶尔这样做,但它不符合 CQRS 模式。我会保留它用于维护任务 - 在系统的主要 CQRS 部分之外。

FindAndModify 操作通常出现在以数据为中心的应用程序中。但是,'FindAndModify' 可以通过一些方式进入 CQRS 应用程序。

FindAndModify 在您的域中可能是一个合法的命令,如果命名不当的话。您将命令路由到执行 'FindAndModify' 的单个聚合。只是要清楚这是一个单一的聚合。我还假设 'FindAndModify' 在您的域中表示某些内容。 另一个地方可能在事件处理程序中。不难想象某种形式的 NameChanged 类型事件。这可能会触发 FindAndModify 样式响应。

我假设您已经对 CQRS 和事件溯源有一定的了解。如果没有,并且您正在寻找有关典型 CQRS ES 应用程序如何组合在一起的良好概述,请查看我的博客 post:CQRS – A Step-by-Step Guide to the Flow of a typical Application

希望有用。