Article

Synchronous vs. Async Projections

Projections are by default asynchronous for command execution, but GUIs need synchronous action and projection generation to ensure correct operation. This article explores when to use each approach and how to balance performance with correctness.

Jorge Leal Portela
Jorge Leal PortelaNovember 15, 2025

In the State-Based Collaboration Framework, projections are read-optimized views derived from the write model (states and events). They enable fast queries, complex aggregations, and context-specific views without complicating the write model.

A fundamental design question is when projections should be computed synchronously versus asynchronously. The default approach is asynchronous: commands execute and generate events, then projections update eventually. This provides better performance and scalability.

However, GUIs have different requirements. When a user triggers an action, they expect to see the result immediately. If projections are updated asynchronously, the UI might show stale data or require complex loading states. This article explores how to balance these competing needs.

Part 1: The Trade-offs

Asynchronous projections (default)

Asynchronous projections update after commands complete. The flow is:

  1. Command executes and generates events
  2. Command returns success to the caller
  3. Projections update asynchronously (eventually)

Benefits:

  • Performance: Commands return quickly without waiting for projection updates
  • Scalability: Projection updates can be batched, queued, or distributed across workers
  • Resilience: If projection updates fail, commands still succeed
  • Decoupling: Write and read models evolve independently

Drawbacks:

  • Stale data: Users might see outdated information immediately after actions
  • Complex UI: Requires loading states, polling, or optimistic updates
  • Race conditions: Users might act on stale data before projections update

Synchronous projections

Synchronous projections update before the command returns. The flow is:

  1. Command executes and generates events
  2. Projections update immediately
  3. Command returns success with updated projection data

Benefits:

  • Consistency: Users always see up-to-date data immediately
  • Simplicity: No need for loading states or optimistic updates
  • Correctness: Prevents actions based on stale data
  • Better UX: Immediate feedback feels more responsive

Drawbacks:

  • Performance: Commands take longer, blocking until projections update
  • Scalability: Harder to scale projection updates independently
  • Tight coupling: Write and read models must be updated together
  • Failure modes: If projection updates fail, the entire command might fail

When to use each approach

Use asynchronous projections for:

  • Background jobs and batch processing
  • Analytics and reporting (can tolerate slight delays)
  • High-throughput systems where performance is critical
  • Operations where eventual consistency is acceptable

Use synchronous projections for:

  • GUI operations where users need immediate feedback
  • Actions that depend on up-to-date projection data
  • Workflows where correctness is more important than performance
  • Operations where stale data could cause user confusion

Part 2: Synchronous Projections for GUI Operations

The framework approach

In the State-Based Collaboration Framework, projections are by default asynchronous for command execution. This provides the performance and scalability benefits mentioned above. However, the framework also supports synchronous projection generation for GUI operations.

The key insight is that GUIs need synchronous action and projection generation to ensure correct operation. When a user triggers an action through the GUI, they expect to see the result immediately. If projections update asynchronously, the UI might show stale data, leading to confusion or incorrect subsequent actions.

Importantly, synchronous projections are not configured by action but are provided by the GUI at runtime. When a GUI triggers an action, it specifies which projections it needs updated synchronously based on what it's currently displaying. This makes the system flexible and allows different GUIs to request different projections for the same action, depending on their context.

Why GUIs need synchronous projections

GUIs have unique requirements that make synchronous projections necessary:

Immediate feedback

Users expect to see the result of their actions immediately. If they submit a form or click a button, they want to see the updated state right away, not wait for an async projection to update.

Correct subsequent actions

After an action, users might immediately take another action based on the result. If the projection is stale, they might make decisions based on outdated information, leading to errors or confusion.

Simplified UI logic

With synchronous projections, the UI doesn't need complex loading states, polling mechanisms, or optimistic update logic. The action completes, and the data is ready.

Consistency guarantees

Synchronous projections ensure that what the user sees matches what actually happened. There's no window where the UI shows stale data after a successful action.

How it works in the framework

The framework supports both modes:

Default (Async):
  1. Action executes → generates events
  2. Action returns success
  3. Projections update asynchronously (eventually)
GUI Mode (Synchronous):
  1. GUI triggers action with list of projections to update
  2. Action executes → generates events
  3. Specified projections update immediately (synchronously)
  4. Action returns success with updated projection data

Synchronous projections are not configured by action but are provided by the GUI at runtime. When a GUI triggers an action, it provides a list of projections it expects to be updated synchronously. The GUI knows which projections it needs because it queries those projections to show data to the user.

This runtime approach means:

  • The same action can be called with different projection lists depending on the GUI context
  • Different GUIs can request different projections for the same action
  • The GUI explicitly declares its dependencies at the point of action execution
  • No static configuration is needed—the system adapts to what each GUI needs

The framework flow:

  • GUI identifies needed projections: The GUI queries projections to display data to the user. It knows which projections it depends on.
  • Action request includes projection list: When triggering an action, the GUI includes a list of projections it expects to be updated synchronously.
  • Action executes: The action executes and generates events (as normal).
  • Synchronous projection updates: The framework immediately computes and updates only the projections specified in the request, synchronously.
  • Returns updated data: The action returns success with the updated projection data for the specified projections.
  • GUI displays immediately: The GUI can display the updated data immediately, ensuring correctness.

This approach is efficient because:

  • Only the projections the GUI actually needs are updated synchronously
  • Other projections can still update asynchronously, maintaining performance
  • The GUI explicitly declares its dependencies, making the system self-documenting
  • No unnecessary synchronous work is performed

Example: Document submission

Consider a document submission workflow:

Scenario: User submits a document for review through the GUI.
Action: SubmitDocument with projection list: ["MyDocumentsList"]
Flow:
  1. GUI queries MyDocumentsList projection to display user's documents
  2. User clicks submit → GUI triggers action with projection list: ["MyDocumentsList"]
  3. Action executes → document state changes to "Submitted"
  4. Event generated: DocumentSubmitted
  5. MyDocumentsList projection updates synchronously
  6. Action returns with updated MyDocumentsList data
  7. GUI displays updated document status immediately
Result: User sees the document in "Submitted" state immediately. They can confidently take the next action (like viewing the submission confirmation) knowing the data is current.

If this were asynchronous, the user might see the document still in "Draft" state for a moment, causing confusion or leading them to try submitting again.

Related content

Explore these related topics:

Explore more articles

This article is part of a series exploring collaboration patterns and practices. Check out other articles or dive into the framework components.