Event Sourcing Pattern for Real-Time Frontends

Event-driven real-time communication for web applications

Pasan Missaka
Bits and Pieces

--

Suppose you are developing a real-time web application that communicates with multiple clients. Such an application would often consist of a backend service that is capable of handling bi-directional messages.

Though it is possible to implement it using traditional CRUD patterns, it is not the most efficient approach.

Why it’s Challenging?

Besides, there are certain caveats to these kinds of systems.

  • One common challenge is the nature of the workflow. It’s not entirely request-response. You have to both submit messages and listen (or subscribed) to receive notifications.
  • Performing updates message-wise against a data store results in low performance and limited scalability due to the overhead required to write each message to the datastore.
  • There could also be concurrency issues that result in low-performance and potential data conflicts.
  • It also limits the traceability of the events.

Event Sourcing to Rescue

This is where Event Sourcing comes into the picture. It is a way to process events update the current state by design.

With this pattern, we modify the state of an entity in our data store by processing a series of changing events.

When you do an action in the frontend, it will generate an event and send it to the backend. Since events describe past events, we can’t make any changes to them as they have already happened. We can only append new events to the existing event queue or the log, making the write operation simple as it requires minimal locking when appending a new event.

Hence events are immutable. Then, the current state will be reconstructed by replaying the events in the series.

Event Sourcing, therefore, provides some significant benefits over the CRUD pattern. Due to these reasons, some organizations are beginning to adopt Event Sourcing with CQRS when adopting the Microservices paradigm.

Event Sourcing for Real-Time Frontends?

Let's discuss in detail how we can use Event Sourcing for real-time frontend applications.

Event Sourcing has a highly efficient write model. Therefore, it fits quite well to handle real-time events sent by the frontend application via Web Sockets. Then, these events can be processed and easily be fed across to any subscribed consumer via Web Sockets.

However, when a significant number of events gets added over time, this approach isn’t efficient in recreating the current state by replaying to all the past events each time.

One way to handle this is by using snapshots. We can create snapshots at specific intervals. For example, it could be a daily snapshot, or the range is dependent on a particular number of events. Then the state can be derived by replaying the events from the time snapshot was taken, eliminating the need of replaying the events from the 0th event.

But, when it comes to real-time events, the snapshot period should come to a very minimum.

We can combine CQRS (Command and Query Responsibility Segregation) and Stream Processing to address this.

CQRS and Stream Processing

CQRS is a pattern that forces to use of different models for reading and writing data.

So, when it comes to real-time data, your frontend can push the data to a Web Socket channel that will go into the Event Bus (which stores the Event Stream). Then the event snapshots are processed, and the reconstructed state is stored in the Data Store by a processing agent subscribed to the Event Bus.

REST API for Queries

Then, the new state is exposed via a REST API for data queries completing the CQRS flow. The REST API is helpful for initial data loading.

But, since we are talking about real-time data, do we have to wait until the final state is constructed to notify the subscribers? The answer is No!.

Stream Processing to Publish Events

You can use another set of subscriber agents to process the events stream and publish events directly to the Web Socket channels. Since it happens in parallel to constructing the state, the responses will be faster.

Suppose you are building a chat application. Then, when a user sends a message from its frontend as an event, we can forward it to relevant Web Socket channels.

If the message is self-contained with data about its destination, minimal processing is needed before forwarding it to relevant channels.

However, if you need to query any metadata related to each event, we can improve the performance by processing events in bulk.

This way, enriching the publishing message becomes faster since we can perform batch queries against the database than processing message by message.

As you can see, Event Sourcing coupled with CQRS and Stream Processing would ultimately benefit real-time applications due to multiple advantages.

CRUD vs Event Sourcing

In addition to what we discussed above, let’s compare CRUD vs. Event Sourcing for real-time applications.

Conclusion

Considering all of the above, we can see that Event Sourcing itself has its complexities. Besides, Event Sourcing is a paradigm shift, and it will put a significant cognitive load on the developers.

But, from the outset, it looks like Event Sourcing complements real-time frontend applications as it solves some of the issues we have with the CRUD approach.

However, it is worth evaluating whether your app needs Event Sourcing in the first place. If your application seemingly doesn’t need to keep the historical state, audit logs high at scale, it’s always best not to complicate it with Event Sourcing.

However, if you follow the Microservice paradigm, it’s worth investigating.

Build Great Design Systems and Micro Frontends

Take frontend development to the next level with independent components. Build and collaborate on component-driven apps to easily unlocks Micro Frontends, and to share components.

OSS Tools like Bit offer a great dev experience for building and composing independent components, and build solo or together with your team.
Give it a try →

A React ‘card’ component independently developed and source-controlled with an auto-generated dependency tree.

--

--