Skip to content

Passive projection

A passive projection is a projection that is not actively materialized to a persistent store but can be queried on-demand for in-memory lookups. This is useful for scenarios where you need to construct read models from events without the overhead of maintaining persistent state.

Use the .Passive() method to mark a projection as passive:

using Cratis.Chronicle.Projections;
public class UserSummaryProjection : IProjectionFor<UserSummary>
{
public void Define(IProjectionBuilderFor<UserSummary> builder) => builder
.Passive()
.AutoMap()
.From<UserCreated>()
.From<UserUpdated>();
}

This projection:

  • Will not be actively maintained in persistent storage
  • Can be queried on-demand using the IProjections service
  • Reconstructs the read model from events when requested

Passive projections are accessed through the event store’s ReadModels using the GetInstanceById method:

public class UserService
{
private readonly IEventStore _eventStore;
public UserService(IEventStore eventStore)
{
_eventStore = eventStore;
}
public async Task<UserSummary?> GetUserSummaryAsync(string userId)
{
return await _eventStore.ReadModels.GetInstanceById<UserSummary>(userId);
}
}

The read model is defined the same way as for regular projections:

public record UserSummary(
string Name,
string Email,
int LoginCount,
DateTimeOffset LastLoginAt);

Events should match the read model structure or use explicit mapping:

[EventType]
public record UserCreated(string Name, string Email);
[EventType]
public record UserUpdated(string Name, string Email);
[EventType]
public record UserLoggedIn(DateTimeOffset LoginTime);

When you call GetInstanceById on a passive projection:

  1. Chronicle retrieves all relevant events for the specified event source ID
  2. The projection logic is applied to reconstruct the read model in memory
  3. The resulting read model is returned without being persisted
  4. Each call reconstructs the model from scratch, ensuring up-to-date data

Passive projections are ideal for:

  • Infrequent queries: When read models are accessed rarely or sporadically
  • Real-time data: When you always need the most current state without caching concerns
  • Memory-sensitive scenarios: When you want to avoid storing projection state
  • Temporary calculations: For read models that are computed and discarded
  • Testing and debugging: When you need to inspect event-driven state without persistence
  • Passive projections have higher latency since they reconstruct on each request
  • They consume more CPU but less storage compared to active projections
  • Consider caching strategies if the same passive projection is accessed frequently
  • Use for read models with simple event processing logic to minimize reconstruction time