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.
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:
- Command executes and generates events
- Command returns success to the caller
- 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:
- Command executes and generates events
- Projections update immediately
- 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:
- Action executes → generates events
- Action returns success
- Projections update asynchronously (eventually)
- GUI triggers action with list of projections to update
- Action executes → generates events
- Specified projections update immediately (synchronously)
- 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:
SubmitDocument with projection list: ["MyDocumentsList"]- GUI queries
MyDocumentsListprojection to display user's documents - User clicks submit → GUI triggers action with projection list:
["MyDocumentsList"] - Action executes → document state changes to "Submitted"
- Event generated:
DocumentSubmitted MyDocumentsListprojection updates synchronously- Action returns with updated
MyDocumentsListdata - GUI displays updated document status immediately
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:
- CQRS Pattern - Separating read and write models
- Framework overview - Learn about projections and events components
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.