3.7 KiB
3.7 KiB
Projections
Build read models from event streams for optimized queries.
Overview
Projections transform event streams into queryable read models. They subscribe to events, build denormalized views, and maintain checkpoints for fault tolerance.
Key Features:
- ✅ IDynamicProjection - Interface for projection implementations
- ✅ Auto-Start - Automatically start on application launch
- ✅ Checkpointing - Track progress for fault tolerance
- ✅ Resettable - Rebuild from scratch when needed
- ✅ Batch Processing - Process events in batches for performance
Quick Start
public class OrderSummaryProjection : IDynamicProjection
{
private readonly IOrderSummaryRepository _repository;
private readonly ICheckpointStore _checkpointStore;
private readonly IEventStreamStore _eventStore;
public string ProjectionName => "order-summary";
public async Task RunAsync(CancellationToken ct)
{
var checkpoint = await _checkpointStore.GetCheckpointAsync(ProjectionName);
await foreach (var @event in _eventStore.ReadStreamAsync(
"orders",
fromOffset: checkpoint + 1,
cancellationToken: ct))
{
await HandleEventAsync(@event);
await _checkpointStore.SaveCheckpointAsync(ProjectionName, @event.Offset);
}
}
private async Task HandleEventAsync(StoredEvent @event)
{
var eventData = JsonSerializer.Deserialize(
@event.Data,
Type.GetType(@event.EventType));
switch (eventData)
{
case OrderPlacedEvent placed:
await _repository.AddOrderSummaryAsync(new OrderSummary
{
OrderId = placed.OrderId,
CustomerName = placed.CustomerName,
TotalAmount = placed.TotalAmount,
Status = "Placed"
});
break;
case OrderShippedEvent shipped:
await _repository.UpdateOrderStatusAsync(shipped.OrderId, "Shipped");
break;
}
}
}
Registration
builder.Services.AddSingleton<IDynamicProjection, OrderSummaryProjection>();
builder.Services.AddHostedService<ProjectionHostedService>();
Features
Creating Projections
Implement IDynamicProjection to build read models from events.
Resettable Projections
Rebuild projections from scratch using IResettableProjection.
Checkpoint Stores
Track projection progress with PostgreSQL or in-memory checkpoints.
Common Patterns
Order Summary:
OrderPlacedEvent → Create OrderSummary
OrderShippedEvent → Update OrderSummary.Status
OrderCancelledEvent → Update OrderSummary.Status
Customer Analytics:
UserRegisteredEvent → Increment TotalUsers
OrderPlacedEvent → Increment CustomerOrderCount
OrderPlacedEvent → Add to RevenueByCustomer
Product Inventory:
InventoryAddedEvent → Increase StockLevel
OrderPlacedEvent → Decrease StockLevel (reservation)
OrderCancelledEvent → Increase StockLevel (release)
Best Practices
✅ DO
- Use checkpoints for fault tolerance
- Process events idempotently
- Monitor projection lag
- Use batch processing for performance
- Separate read and write models
❌ DON'T
- Don't skip checkpointing
- Don't modify projection logic without rebuilding
- Don't ignore projection lag
- Don't query write model for reads