30.06.2026 • 5 min read

DesignFlix 15: Build a Read View Without Losing Track of Updates

An order-history page looks simple until every row needs information from three services.

The page asks for orders, then customer details, then delivery status. Each lookup adds work and another dependency to the request. If the page is visited often, we can prepare a view ahead of time instead of assembling the same information on every visit.

Consume and project is the pattern behind that approach: consume changes, then maintain the data shape the page needs. Let's build an order view and examine what happens when updates arrive late.

Make the page's question explicit

Suppose a support dashboard needs an order number, customer display name, total and current status. It doesn't need every field the order, customer and delivery services store.

A projection is a derived representation organised for that question. The dashboard can read one prepared record instead of making several service calls for each row.

This isn't free performance. We move work from request time to update time, and we store another copy that somebody has to maintain. The materialized-view pattern describes that trade-off.

Consume changes and update the view

An order event arrives. The projection handler extracts the fields it needs, obtains any required related data, then writes the dashboard record.

The original consume-and-project flow from events to an enriched read view

For a new order, the handler might fetch the customer's display name. That removes the lookup from the page request, but doesn't remove the dependency completely. If the customer service is unavailable, the handler still needs a retry or an explicit incomplete-view policy.

Also decide what the name means. Is it the current profile name, or the name recorded when the order was placed? A later lookup doesn't necessarily tell us a historical fact. The view's meaning should answer that question before its implementation quietly chooses for us.

A late update shouldn't move the view backwards

Suppose order 318 reaches version 7 with status shipped. An older full-state event for version 6 arrives afterwards with status paid.

A blind update would make the dashboard go backwards. For full-state updates from one ordered source, we can keep the source version and reject an older or repeated version atomically.

Here's an illustrative SQL update for SQLite-compatible syntax:

INSERT INTO order_view (order_id, status, source_version)
VALUES (318, 'shipped', 7)
ON CONFLICT(order_id) DO UPDATE SET
  status = excluded.status,
  source_version = excluded.source_version
WHERE excluded.source_version > order_view.source_version;

The condition belongs inside the write. Reading the version in application code and then writing later leaves room for another worker to change it between those steps.

This example assumes every event supplies the full status state needed by this view. For incremental events such as "add one item," skipping a missing earlier event can lose information. Those handlers need ordered processing, gap detection or another recovery strategy appropriate to their event model.

Sherlock scene

Version seven? Bit late, aren't you?

Sherlock · Tenor · Adapted dialogue

An event ID and a version answer different questions

An event ID helps identify repeated delivery of the same event. A source version helps place a change within one source's sequence.

Neither automatically gives us one global ordering across the customer, order and delivery services. Keep progress and conflict rules appropriate to each source. If a view combines them, define how the fields relate rather than comparing unrelated sequence numbers.

Record processing progress durably with the local view update where the storage design allows it. A crash between writing the view and acknowledging a message can cause a repeat, so the handler still needs a safe replay policy.

If the view looks correct now, can we explain which source updates it has actually processed?

Show freshness where it changes a decision

The dashboard may briefly lag behind a completed update. Support staff need to understand that before using the display to make a promise to a customer.

Track update lag and source progress. For actions that change the order, validate against the authoritative service rather than treating the dashboard copy as permission. A convenient read view isn't a new owner of the business rules.

If a stale result would be unacceptable, use a read path or consistency mechanism that meets that requirement. Not every screen needs an asynchronously maintained view.

Rebuilding requires enough source data

One attraction of a derived view is that we can rebuild it. But a queue that retained only yesterday's changes won't recreate all orders from last year.

Plan a source snapshot, replayable history or controlled backfill. Agree on where that initial data ends and live updates begin, so a rebuild doesn't miss or overwrite changes in the gap. Test the new view before switching readers to it.

Consume-and-project doesn't require the whole application to use event sourcing. It does require enough source information to maintain and recover this particular projection.

Give the page a view you can maintain

Our order dashboard now has a focused read shape. Its page requests are simpler, while update handling owns enrichment, version checks, freshness and recovery.

That's a useful exchange when the page's repeated assembly work is a real problem. Before adopting it, describe not only what fields make the page fast, but how those fields become correct again after the updater stops. A prepared answer is valuable when we can explain both its meaning and its age.

Try it yourself

Version 7 says shipped and is already in the view. Version 6 says paid and arrives late. Can every projection safely discard version 6?

A possible solution

For this full-state status view, an atomic source-version check can reject the old update. That rule is not universal: an incremental handler might still need an earlier event to preserve a count, balance or sequence. Define whether events are full state or deltas, detect required gaps, and keep version comparisons within the same source stream.

Share this article with your friends

Know someone who would enjoy this? Send it their way.

WhatsAppLinkedInXEmail