📜 ⬆️ ⬇️

Optimistic UI, CQRS and EventSourcing

Optimistic UI, CQRS and EventSourcing


When developing high-load web applications, a principle such as CQRS is often used for better scaling. It states that a method must be either a command that performs some action, or a query that returns data, but not both. In other words, the question to the system should not change the answer. More formally, only pure methods that have no side effects can be returned.


But for good scaling the separation of the API for reading / writing is not enough. It is necessary to separate the databases with which this API works. This is where EventSourcing comes to the rescue. He suggests that we store all system events in one database, let's call it EventStore, and build all the other databases and tables based on it.


The combination of CQRS and EventSourcing unleashes us a lot in terms of load balancing within the system, the number of its nodes, the number of auxiliary databases, the use of caching and other things, but at the same time complicates the logic of the application and introduces many restrictions.


In this article, we will consider one of the nuances of designing the client part for such a system - optimistic updates in the UI.


For the frontend, take the trendy React and Redux. By the way, Redux and EventSourcing are very close in spirit technology.


Optimistic user interface updates are not so easy to implement, and CQRS and EventSourcing make the task even harder.


How should this work? Let's look at step by step.


  1. We send the team and, without waiting for an answer, dispute an optimistic event in the Redux Store. An optimistic event will contain the expected server results. Also at this step, we remember the current state of the data that the event will change.


  2. We are waiting for the result of sending the command. If the command fails, dispatch the event, rolling back the optimistic update, based on the data that was remembered in the first step. If everything is good, then we do nothing.


  3. We are waiting for the real event to fly to the client from the bus. When this happens, we roll back the optimistic update and apply the real event.

How it will look in practice:


SuccessFailure
optimistic-successoptimistic-failure
optimistic-success-reduxoptimistic-failure-redux

The optimistic update code is described as Middleware to the Redux Store:


const optimisticCalculateNextHashMiddleware = (store) => { const tempHashes = {}; const api = createApi(store); return next => action => { switch (action.type) { case SEND_COMMAND_UPDATE_HASH_REQUEST: { const { aggregateId, hash } = action; // Save the previous data const { hashes } = store.getState() const prevHash = hashes[aggregateId].hash; tempHashes[aggregateId] = prevHash // Dispatch an optimistic action store.dispatch({ type: OPTIMISTIC_HASH_UPDATED, aggregateId, hash }); // Send a command api.sendCommandCalculateNextHash(aggregateId, hash) .then( () => store.dispatch({ type: SEND_COMMAND_UPDATE_HASH_SUCCESS, aggregateId, hash }) ) .catch( (err) => store.dispatch({ type: SEND_COMMAND_UPDATE_HASH_FAILURE, aggregateId, hash }) ); break; } case SEND_COMMAND_UPDATE_HASH_FAILURE: { const { aggregateId } = action; const hash = tempHashes[aggregateId]; delete tempHashes[aggregateId]; store.dispatch({ type: OPTIMISTIC_ROLLBACK_HASH_UPDATED, aggregateId, hash }); break; } case HASH_UPDATED: { const { aggregateId } = action; const hash = tempHashes[aggregateId]; delete tempHashes[aggregateId]; store.dispatch({ type: OPTIMISTIC_ROLLBACK_HASH_UPDATED, aggregateId, hash }); break; } } next(action); } } 

Live how everything works, you can see here:



Conclusion


Optimistic updates in the UI can greatly improve the responsiveness of your application. Although you need to use them wisely and with great caution. In some cases, they can lead to data loss and complicate understanding of the user interface. For example, an optimistic like under a photo is good, and an optimistic form of payment is bad. So do not break the wood. Good luck!


')

Source: https://habr.com/ru/post/350060/


All Articles