131 lines
3.7 KiB
Markdown
131 lines
3.7 KiB
Markdown
# 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
|
|
|
|
```csharp
|
|
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
|
|
|
|
```csharp
|
|
builder.Services.AddSingleton<IDynamicProjection, OrderSummaryProjection>();
|
|
builder.Services.AddHostedService<ProjectionHostedService>();
|
|
```
|
|
|
|
## Features
|
|
|
|
### [Creating Projections](creating-projections.md)
|
|
Implement IDynamicProjection to build read models from events.
|
|
|
|
### [Resettable Projections](resettable-projections.md)
|
|
Rebuild projections from scratch using IResettableProjection.
|
|
|
|
### [Checkpoint Stores](checkpoint-stores.md)
|
|
Track projection progress with PostgreSQL or in-memory checkpoints.
|
|
|
|
## Common Patterns
|
|
|
|
**Order Summary:**
|
|
```csharp
|
|
OrderPlacedEvent → Create OrderSummary
|
|
OrderShippedEvent → Update OrderSummary.Status
|
|
OrderCancelledEvent → Update OrderSummary.Status
|
|
```
|
|
|
|
**Customer Analytics:**
|
|
```csharp
|
|
UserRegisteredEvent → Increment TotalUsers
|
|
OrderPlacedEvent → Increment CustomerOrderCount
|
|
OrderPlacedEvent → Add to RevenueByCustomer
|
|
```
|
|
|
|
**Product Inventory:**
|
|
```csharp
|
|
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
|
|
|
|
## See Also
|
|
|
|
- [Event Streaming Overview](../README.md)
|
|
- [Persistent Streams](../fundamentals/persistent-streams.md)
|
|
- [Event Replay](../event-replay/README.md)
|