How do you ensure consistent client reads in an eventual consistent system?
Asked Answered
A

3

7

I'm digging into CQRS and I am looking for articles on how to solve client reads in an eventual consistent system. Consider for example a web shop where users can add items to their cart. How can you ensure that the client displays items in the cart if the actual processing of the command "AddItemToCart" is done async? I understand the principles of dispatching commands async and updating the read model async based on domain events, but I fail to see how this is handled from the clients perspective.

Antineutrino answered 30/4, 2012 at 7:8 Comment(2)
According to Udi Dahan, Shopping carts are a classic example of a domain where CQRS isn't a good fit.Melancholia
What about amazon? They do eventual consistency. Anyways its besides the point. I am not creating an eventual consistent system I am simply asking how this can be done.Antineutrino
O
5

There are a few different ways of doing it;

Wait at user till consistent

Just poll the server until you get the read model updated. This is similar to what Ben showed.

Ensure consistency through 2PC

You have a queue that supports DTC; and your commands are put there first. They are then; executed, events sent, read model updated; all inside a single transaction. You have not actually gained anything with this method though, so don't do it this way.

Fool the client

Place the read models in local storage at the client and update them when the corresponding event is sent -- but you were expecting this event anyway, so you had already updated the javascript view of the shopping cart.

Orchestrion answered 3/5, 2012 at 15:49 Comment(5)
I don't understand what you mean by opt 2. DTC in NServiceBus just ensures consistency between the input/output queue and any database operation done when processing message. There is no single transaction between endpoints.Antineutrino
NServiceBus - what has that got to do with anything? That's how NServiceBus works, and that's fine with me.Orchestrion
Hmm, maybe you mean; how would I go about creating this consistency boundary that spans both queue and sending? You'd have to share the transaction handle between the sender and the receiver.Orchestrion
No, I mean what I wrote in the question. The read model should be considered an autonomous component and any failure to update the read model should not result in a rollback in the write model. Anyway I agree that opt 2 (if one used a technology where you could do something like that) sounds like a bad idea.Antineutrino
Well I guess alternative 1 and 3 for you then.Orchestrion
C
2

I'd recommend you have a look at the Microsoft Patterns & Practices team's guidance on CQRS. Although this is still work-in-progress they have given one solution to the issue you've raised.

Their approach for commands requiring feedback is to submit the command asynchronously, redirect to another controller action and then poll the read model for the expected change or a time-out occurs. This is using the Post-Redirect-Get pattern which works better with the browser's forward and back navigation buttons, and gives the infrastructure more time to process the command before the MVC controller starts polling.

Example code from the RegistrationController using ASP.NET MVC 4 asynchronous controllers.

[HttpGet]
[OutputCache(Duration = 0, NoStore = true)]
public Task<ActionResult> SpecifyRegistrantAndPaymentDetails(Guid orderId, int orderVersion)
{
    return this.WaitUntilOrderIsPriced(orderId, orderVersion)
        .ContinueWith<ActionResult>(

        ...

    );
}

...

private Task<PricedOrder> WaitUntilOrderIsPriced(Guid orderId, int lastOrderVersion)
{
    return
        TimerTaskFactory.StartNew<PricedOrder>(
            () => this.orderDao.FindPricedOrder(orderId),
            order => order != null && order.OrderVersion > lastOrderVersion,
            PricedOrderPollPeriodInMilliseconds,
            DateTime.Now.AddSeconds(PricedOrderWaitTimeoutInSeconds));
}

I'd probably use AJAX polling instead of having a blocked web request at the server.

Czerny answered 30/4, 2012 at 12:30 Comment(2)
Btw; DateTime.Now experiences all types of issues and should be avoided (and it's localized and there are leap-seconds etc). Also Thread.Sleep(500) is a big no-no on a web server serving requests; it's better to use a task-driven UI.Orchestrion
@Orchestrion I've just updated my answer with the latest example code from the CQRS guidance using ASP.NET MVC 4 asynchronous controllers. No more Thread.Sleep!Czerny
L
2

Post-Redirect-Get

You're hoping that the save command executes on time before Get is called. What if the command takes 10 seconds to complete in the back end but Get is called in 1 second?

Local Storage

With storing the result of the command on the client while the command goes off to execute, you're assuming that the command will go through without errors. What if the back-end runs into an error while processing the command? Then what you have locally isn't consistent.

Polling

Polling seems to be the option that is actually in line with eventual consistency; you're not faking or assuming. Your polling mechanism can be an asynchronous as a part of your page, e.g. shopping cart page component polls until it gets an update without refreshing the page.

Callbacks

You could introduce something like web hooks to make a call back to the client if the client is capable of receiving such. By providing a correlation Id once the command is accepted by the back-end, once the command has finished processing, the back-end can notify the front end of the command's status along with the correlation Id on whether the command went through successfully or not. There is no need for any kind of polling with this approach.

Latitudinarian answered 4/5, 2017 at 21:27 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.