Speculative Execution
Speculative execution runs commands against temporal state without persisting results. The aggregate state remains unchanged after speculative execution.
Use Cases
| Use Case | Description |
|---|---|
| Form Validation | "Will this order succeed?" before user commits |
| Preview | "What events would this command produce?" |
| Testing | Verify business logic without polluting event store |
| Dry Run | Check if command would be accepted before executing |
SpeculativeClient
The SpeculativeClient provides speculative execution across all coordinator types:
| Method | Description |
|---|---|
aggregate() | Test commands against aggregate state |
projector() | Test projections against events |
saga() | Test saga execution against events |
process_manager() | Test PM execution |
Aggregate Speculative Execution
Test a command against temporal state (existing events + hypothetical events):
- Rust
- Go
- Python
- Java
- C#
- C++
use angzarr_client::SpeculativeClient;
use angzarr_client::proto::SpeculateAggregateRequest;
// Connect to speculative client
let client = SpeculativeClient::connect("http://localhost:1310").await?;
// Build speculative request with temporal state
let request = SpeculateAggregateRequest {
command: Some(command_book),
events: prior_events, // Events to apply before command
};
// Execute without persistence
let response = client.aggregate(request).await?;
// Inspect what events WOULD be produced
for page in response.events.as_ref().unwrap().pages.iter() {
println!("Would produce: {}", page.event.as_ref().unwrap().type_url);
}
// Original aggregate is unchanged!
import (
angzarr "github.com/benjaminabbitt/angzarr/client/go"
pb "github.com/benjaminabbitt/angzarr/client/go/proto/angzarr"
)
// Connect to speculative client
client, err := angzarr.NewSpeculativeClient("localhost:1310")
if err != nil {
log.Fatal(err)
}
defer client.Close()
// Build speculative request with temporal state
request := &pb.SpeculateAggregateRequest{
Command: commandBook,
Events: priorEvents, // Events to apply before command
}
// Execute without persistence
response, err := client.Aggregate(ctx, request)
if err != nil {
log.Fatal(err)
}
// Inspect what events WOULD be produced
for _, page := range response.Events.Pages {
log.Printf("Would produce: %s", page.Event.TypeUrl)
}
// Original aggregate is unchanged!
from angzarr_client import SpeculativeClient
from angzarr_client.proto.angzarr import SpeculateAggregateRequest
# Connect to speculative client
client = SpeculativeClient.connect("localhost:1310")
# Build speculative request with temporal state
request = SpeculateAggregateRequest(
command=command_book,
events=prior_events # Events to apply before command
)
# Execute without persistence
response = client.aggregate(request)
# Inspect what events WOULD be produced
for page in response.events.pages:
print(f"Would produce: {page.event.type_url}")
# Original aggregate is unchanged!
client.close()
import dev.angzarr.client.SpeculativeClient;
import dev.angzarr.SpeculateAggregateRequest;
// Connect to speculative client
try (SpeculativeClient client = SpeculativeClient.connect("localhost:1310")) {
// Build speculative request with temporal state
SpeculateAggregateRequest request = SpeculateAggregateRequest.newBuilder()
.setCommand(commandBook)
.addAllEvents(priorEvents) // Events to apply before command
.build();
// Execute without persistence
CommandResponse response = client.aggregate(request);
// Inspect what events WOULD be produced
for (EventPage page : response.getEvents().getPagesList()) {
System.out.println("Would produce: " + page.getEvent().getTypeUrl());
}
// Original aggregate is unchanged!
}
using Angzarr.Client;
// Connect to speculative client
using var client = SpeculativeClient.Connect("http://localhost:1310");
// Build speculative request with temporal state
var request = new SpeculateAggregateRequest
{
Command = commandBook,
Events = { priorEvents } // Events to apply before command
};
// Execute without persistence
var response = client.Aggregate(request);
// Inspect what events WOULD be produced
foreach (var page in response.Events.Pages)
{
Console.WriteLine($"Would produce: {page.Event.TypeUrl}");
}
// Original aggregate is unchanged!
#include <angzarr/client.hpp>
// AggregateClient supports speculative execution directly
auto client = angzarr::AggregateClient::connect("localhost:1310");
// Build speculative request with temporal state
angzarr::SpeculateAggregateRequest request;
*request.mutable_command() = command_book;
for (const auto& event : prior_events) {
*request.add_events() = event; // Events to apply before command
}
// Execute without persistence
auto response = client->handle_sync_speculative(request);
// Inspect what events WOULD be produced
for (const auto& page : response.events().pages()) {
std::cout << "Would produce: " << page.event().type_url() << std::endl;
}
// Original aggregate is unchanged!
Form Validation Example
Validate a user action before they commit:
User fills out order form:
- Product: Widget (ID: widget-123)
- Quantity: 100
- Payment: Credit Card ending 4242
Before "Place Order" button is enabled:
1. Client sends speculative command to Order aggregate
2. Order aggregate checks:
- Does product exist? ✓
- Is quantity available? ✓
- Is payment method valid? ✓
3. Returns projected OrderCreated event
If speculative execution succeeds → Enable "Place Order" button
If speculative execution fails → Show validation error
No events persisted, no side effects!
Saga Speculative Execution
Test how a saga would react to events:
- Rust
- Go
- Python
use angzarr_client::proto::SpeculateSagaRequest;
let request = SpeculateSagaRequest {
events: source_events, // Events the saga would receive
destination: Some(destination_event_book), // Target aggregate state
};
let response = client.saga(request).await?;
// Inspect what commands the saga WOULD emit
for command in response.commands.iter() {
println!("Would send command to: {}", command.cover.domain);
}
request := &pb.SpeculateSagaRequest{
Events: sourceEvents, // Events the saga would receive
Destination: destinationEventBook, // Target aggregate state
}
response, err := client.Saga(ctx, request)
// Inspect what commands the saga WOULD emit
for _, cmd := range response.Commands {
log.Printf("Would send command to: %s", cmd.Cover.Domain)
}
from angzarr_client.proto.angzarr import SpeculateSagaRequest
request = SpeculateSagaRequest(
events=source_events, # Events the saga would receive
destination=destination_book, # Target aggregate state
)
response = client.saga(request)
# Inspect what commands the saga WOULD emit
for cmd in response.commands:
print(f"Would send command to: {cmd.cover.domain}")
Projector Speculative Execution
Test how a projector would process events:
- Rust
- Go
- Python
use angzarr_client::proto::SpeculateProjectorRequest;
let request = SpeculateProjectorRequest {
events: events_to_project,
};
let response = client.projector(request).await?;
// Inspect the projection result
println!("Projection: {:?}", response);
request := &pb.SpeculateProjectorRequest{
Events: eventsToProject,
}
response, err := client.Projector(ctx, request)
log.Printf("Projection: %v", response)
from angzarr_client.proto.angzarr import SpeculateProjectorRequest
request = SpeculateProjectorRequest(events=events_to_project)
response = client.projector(request)
print(f"Projection: {response}")
Testing with Speculative Execution
Speculative execution is excellent for integration tests:
def test_order_creation_produces_correct_events():
client = SpeculativeClient.connect("localhost:1310")
# Create order command
command = build_create_order_command(
customer_id="cust-123",
items=[{"product": "widget", "quantity": 5}]
)
request = SpeculateAggregateRequest(command=command, events=[])
response = client.aggregate(request)
# Verify the projected events
assert len(response.events.pages) == 1
assert response.events.pages[0].event.type_url.endswith("OrderCreated")
# No state changed - can run this test repeatedly!
client.close()
Next Steps
- Error Handling — Error types and introspection methods
- Clients — Client types and connection patterns
- Builders — Fluent API for commands and queries