2

I've been added to an project under development. It's an ASP.Net MVC 5 application using Mediatr and the CQRS pattern(with a read-only database and a write only database - eventually consistent). The application has an admin piece that has a lot of CRUD operations and we're running into problems. For example, let's say there's a 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();
        }
    }
}

In the Create and Edit actions, a Widget is either created or edited and those changes are made to the write-only database. This is followed by an immediate redirect to the Index Action, which reads from the read-only database. The changes are generally not synced from the write-only database by the time this call completes and the view is rendered. To work around this issue, we've used Redis to cache the newly created or updated object and then if the list retrieved in the Index action doesn't contain the new or edited object, we retrieve it from the cache. This feels really, really wrong.

Since none of us on the project have ever been involved in a CQRS project, we don't know how to rectify this issue. It feels like we're really missing something with this pattern.

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?

Matt M
  • 3,699
  • 5
  • 48
  • 76

3 Answers3

4

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?

Many of the pieces you need are already in place.

One way of thinking about CQRS is the writes happen against a live representation of the domain model, but reads happen against cached representations.

HTTP has a pretty good understanding of caching. In particular, HTTP aware caches understand that unsafe operations invalidate cached representations. So any non-error response to a POST request will invalidate the cached data.

You even have a status code for dealing with the eventual consistency; 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, the payload is a "representation of the status of the action".

So you could send to the client a 202 Accepted with a link to a status monitor, that has the information necessary to know if the read model has been updated. For instance, the write model might know what "version" of the object you are waiting for, or how many events (if you are doing event sourcing), or a correlation identifier for the post itself.
Encode this meta data into the target resource for the status monitor, and the client can poll the status monitor until it indicates that the read model has been updated.

There's even an understanding of weak validators, which could be returned by the modification operation to indicate a version of the resource, without needing to know the specifics of the representation that would be needed for a strong validator.

But... I admit I don't find that the assortment of parts produces a satisfying whole. It feels like there is a piece missing - something that lets us return a validator with the representation of the status of the action that helps the client to make the appropriate conditional request to read the representation.

Community
  • 1
  • 1
VoiceOfUnreason
  • 52,766
  • 5
  • 49
  • 91
4

In CQRS the write persistence and the read persistence are separate (logically, temporally and even physically) and what you are experiencing is normal, you should embrace this and not see it as a fundamental problem. Instead you should modify your client to compensate for this. For example you should make the call to update the entities in the background (i.e. AJAX) instead of redirecting the client to the edit page. This is one of the simplest solutions. Others are more complicated, for example using correlation or cauzal identifiers, last modified timestamps, waiting in the Application or Presentation layer for the read model to process the events etc.

Constantin Galbenu
  • 16,951
  • 3
  • 38
  • 54
2

very quick search about how to handle the eventual consistency. In my projects we rely mainly on websocket notifications (i.e. SignalR), but sometime "fake responses" is a good enough approach.

I'd like to know why you use CQRS, specially for CRUD operations. It seems how if you are overengineering your design. Asyncronicity comes with a lot of issues to face

wilver
  • 2,106
  • 1
  • 19
  • 26