Perspective Rebuild¶
Verified by tests
PerspectiveRebuilderTests, PerspectiveRebuilderIntegrationTests, SystemCommandsTests — library CI run #31657041675 (2026-08-13)
When a perspective's schema changes or data becomes stale, Whizbang provides multiple rebuild modes to reconstruct read models from event history.
IPerspectiveRebuilder¶
IPerspectiveRebuilder
public interface IPerspectiveRebuilder {
Task<RebuildResult> RebuildBlueGreenAsync(string perspectiveName, CancellationToken ct = default);
Task<RebuildResult> RebuildInPlaceAsync(string perspectiveName, CancellationToken ct = default);
Task<RebuildResult> RebuildStreamsAsync(string perspectiveName, IEnumerable<Guid> streamIds, CancellationToken ct = default);
Task<RebuildStatus?> GetRebuildStatusAsync(string perspectiveName, CancellationToken ct = default);
}
Rebuild Modes¶
Blue-Green¶
Create a new table, replay all events into it, then atomically swap with the old table. The old table is kept as a backup.
Blue-Green
var result = await rebuilder.RebuildBlueGreenAsync("OrderPerspective");
// App continues serving reads from old table during rebuild
// Swap is atomic — no downtime
Best for: Production deployments where zero-downtime is required.
In-Place¶
Truncate the active table and replay all events directly. Faster but causes temporary data unavailability during replay.
In-Place
Best for: Development, staging, or maintenance windows.
Selected Streams¶
Replay events for specific streams only. Useful for fixing individual corrupted or stale projections without rebuilding everything.
Selected Streams
var corruptedStreams = new[] { orderId1, orderId2 };
var result = await rebuilder.RebuildStreamsAsync("OrderPerspective", corruptedStreams);
Best for: Targeted fixes for specific aggregates.
RebuildResult¶
RebuildResult
public record RebuildResult(
string PerspectiveName,
int StreamsProcessed,
int EventsReplayed,
TimeSpan Duration,
bool Success,
string? Error);
System Commands¶
Trigger rebuilds across distributed services via messaging:
System Commands
// Rebuild specific perspectives
await dispatcher.SendAsync(new RebuildPerspectiveCommand(
PerspectiveNames: ["OrderPerspective", "InventoryPerspective"],
Mode: RebuildMode.BlueGreen));
// Rebuild all perspectives in-place
await dispatcher.SendAsync(new RebuildPerspectiveCommand(
Mode: RebuildMode.InPlace));
// Rebuild specific streams only
await dispatcher.SendAsync(new RebuildPerspectiveCommand(
PerspectiveNames: ["OrderPerspective"],
IncludeStreamIds: [orderId1, orderId2]));
// Cancel an in-progress rebuild
await dispatcher.SendAsync(new CancelPerspectiveRebuildCommand("OrderPerspective"));
System Events¶
The rebuild system emits events for observability:
| Event | When |
|---|---|
PerspectiveRebuildStarted |
Rebuild begins |
PerspectiveRebuildProgress |
Periodically during rebuild |
PerspectiveRebuildCompleted |
Rebuild finishes successfully |
PerspectiveRebuildFailed |
Rebuild fails |
Subscribe via standard receptors for logging, alerting, or dashboards.
Migration-Triggered Rebuilds¶
When the migration system detects a destructive schema change (column type changed or removed), it records the perspective with status 4 (MigratingInBackground). The PerspectiveMigrationWorker background service picks this up on startup and automatically triggers a blue-green rebuild.
See Migration Tracking for details.
PerspectiveStatusModel¶
A built-in read model tracks all perspective health:
PerspectiveStatusModel
public sealed record PerspectiveStatusModel {
[StreamId]
public Guid Id { get; init; }
[PhysicalField(Indexed = true)]
public string PerspectiveName { get; init; } = "";
[PhysicalField]
public PerspectiveState State { get; init; } // Active, Rebuilding, MigratingBlueGreen, Failed, Stale
public string? SchemaHash { get; init; }
public DateTimeOffset? LastRebuildStartedAt { get; init; }
public DateTimeOffset? LastRebuildCompletedAt { get; init; }
public TimeSpan? LastRebuildDuration { get; init; }
public RebuildMode? LastRebuildMode { get; init; }
public string? LastError { get; init; }
public DateTimeOffset LastUpdatedAt { get; init; }
}
Query via Lens for operational dashboards.