CQRS 和最终一致性

CQRS and eventual consistency

我被添加到一个正在开发的项目中。这是一个使用 Mediatr 和 CQRS 模式的 ASP.Net MVC 5 应用程序(具有只读数据库和只写数据库 - 最终一致)。该应用程序有一个管理部分,其中有很多 CRUD 操作,我们 运行 遇到了问题。例如,假设有一个 Widget Controller:

public class WidgetController : Controller
{
    private readonly IMediator _mediator;

    public WidgetController(IMediator mediator)
    {
        _mediator = mediator;
    }

    // GET: Widget
    public ActionResult Index()
    {
        // Reads from the read-only database and may not have synced
        // This call is not guaranteed to have the newly added or edited widget (and usually doesn't)
        var allWidgets = _mediator.Send(new GetAllWidgets());
        return View(allWidgets);
    }

    [HttpPost]
    public ActionResult Create(Widget widget)
    {
        try
        {
            // This call contains the database logic to write into the write only database
            _mediator.Send(new CreateWidget(widget));
            return RedirectToAction("Index");
        }
        catch
        {
            return View();
        }
    }

    [HttpPost]
    public ActionResult Edit(Widget updatedWidget)
    {
        try
        {
            // Writes to the write-only database
            _mediator.Send(new UpdateWidget(updatedWidget));
            return RedirectToAction("Index");
        }
        catch
        {
            return View();
        }
    }
}

在“创建”和“编辑”操作中,创建或编辑了一个小部件,并且对只写数据库进行了这些更改。随后立即重定向到从只读数据库读取的索引操作。在此调用完成和呈现视图时,更改通常不会从只写数据库同步。为了解决这个问题,我们使用 Redis 来缓存新创建或更新的对象,然后如果在 Index 操作中检索到的列表不包含新的或编辑过的对象,我们会从缓存中检索它。 这感觉真的非常错误。

由于我们项目中的 none 人曾经参与过 CQRS 项目,所以我们不知道如何解决这个问题。感觉我们真的缺少这种模式的东西。

所以,我想我想问的是...是否有处理此类情况的最佳实践?有更好的方法吗?

非常快速地搜索 how to handle the eventual consistency. In my projects we rely mainly on websocket notifications (i.e. SignalR),但有时 "fake responses" 是一个足够好的方法。

我想知道为什么你使用CQRS,专门用于CRUD操作。如果你过度设计你的设计,这似乎是怎样的。 Asyncronicity comes with a lot of issues to face

So, I guess what I'm asking is this...is there a best practice to handle this type of scenario? Is there a better way to do this?

您需要的许多部件已经到位。

考虑 CQRS 的一种方式是写入发生在域模型的实时表示上,而读取发生在缓存的表示上。

HTTP 对 caching. In particular, HTTP aware caches understand that unsafe operations invalidate 缓存表示有很好的理解。因此,对 POST 请求的任何非错误响应都将使缓存数据无效。

你甚至有一个处理最终一致性的状态码; 202 Accepted announces to intermediaries that the request is not an error (invalidating caches) but "processing has not been completed". Much like for a 200 response,有效负载是“动作状态的表示”。

因此,您可以向客户端发送带有 link 的 202 Accepted 到状态监视器,它具有了解读取模型是否已更新所需的信息。例如,写入模型可能知道您正在等待的对象的“版本”,或者有多少事件(如果您正在执行事件源),或者 post 本身的 correlation identifier
将此元数据编码到状态监视器的目标资源中,客户端可以轮询状态监视器,直到指示读取模型已更新。

甚至可以理解弱验证器,它可以通过修改操作 return 来指示资源的版本,而无需知道强验证器所需的表示的具体细节验证者。

但是...我承认我没有发现零件的分类产生令人满意的整体。感觉好像少了一块 - 让我们 return 一个具有动作状态表示的验证器,帮助客户端发出适当的条件请求来读取表示。

在 CQRS 中,写入持久性和读取持久性是分开的(逻辑上、时间上什至物理上),您遇到的是正常现象,您应该接受这一点,而不是将其视为根本问题。相反,您应该修改您的客户端以对此进行补偿。例如,您应该调用以在后台更新实体(即 AJAX),而不是将客户端重定向到编辑页面。这是最简单的解决方案之一。其他的更复杂,例如使用相关性或因果标识符、最后修改的时间戳、在应用程序或表示层等待读取模型处理事件等。