dotnet-cqrs/docs/event-streaming/projections/README.md

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

See Also