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.
Defining a passive projection
Section titled “Defining a passive projection”Use the .Passive() method to mark a projection as passive:
using Cratis.Chronicle.Projections;
public class DecPassiveUserSummaryProjection : IProjectionFor<DecPassiveUserSummary>{ public void Define(IProjectionBuilderFor<DecPassiveUserSummary> builder) => builder .Passive() .AutoMap() .From<DecPassiveUserCreated>() .From<DecPassiveUserUpdated>();}Kotlin does not support this workflow yet.Java does not support this workflow yet.defmodule MyApp.Projections.DecPassiveUserSummaryProjection do use Chronicle.Projections.Projection, model: MyApp.ReadModels.DecPassiveUserSummary, passive: true
alias MyApp.Events.{DecPassiveUserCreated, DecPassiveUserUpdated}
from DecPassiveUserCreated from DecPassiveUserUpdatedendimport { IProjectionBuilderFor, IProjectionFor, projection } from '@cratis/chronicle';
@projection()class DecPassiveUserSummaryProjection implements IProjectionFor<DecPassiveUserSummary> { define(builder: IProjectionBuilderFor<DecPassiveUserSummary>): void { builder .passive() .autoMap() .from(DecPassiveUserCreated) .from(DecPassiveUserUpdated); }}This projection:
- Will not be actively maintained in persistent storage
- Can be queried on-demand using the
IProjectionsservice - Reconstructs the read model from events when requested
Using passive projections
Section titled “Using passive projections”Passive projections are accessed through the event store’s ReadModels using the GetInstanceById method:
using Cratis.Chronicle;
public class DecPassiveUserService(IEventStore eventStore){ public Task<DecPassiveUserSummary> GetUserSummary(string userId) => eventStore.ReadModels.GetInstanceById<DecPassiveUserSummary>(userId);}Kotlin does not support this workflow yet.Java does not support this workflow yet.defmodule MyApp.UserService do def get_user_summary(user_id) do Chronicle.ReadModels.get_instance_by_id(MyApp.ReadModels.DecPassiveUserSummary, user_id) endendimport { ChronicleClient } from '@cratis/chronicle';
class DecPassiveUserService { constructor(private readonly client: ChronicleClient) {}
async getUserSummary(userId: string): Promise<DecPassiveUserSummary> { const store = await this.client.getEventStore('MyStore'); return store.readModels.getInstanceById(DecPassiveUserSummary, userId); }}Read model definition
Section titled “Read model definition”The read model is defined the same way as for regular projections:
public record DecPassiveUserSummary( string Name, string Email, int LoginCount, DateTimeOffset LastLoginAt);Kotlin does not support this workflow yet.Java does not support this workflow yet.defmodule MyApp.ReadModels.DecPassiveUserSummary do use Chronicle.ReadModels.ReadModel
defstruct [:name, :email, login_count: 0, last_login_at: nil]endclass DecPassiveUserSummary { name = ''; email = ''; loginCount = 0; lastLoginAt = new Date();}Event definitions
Section titled “Event definitions”Events should match the read model structure or use explicit mapping:
using Cratis.Chronicle.Events;
[EventType]public record DecPassiveUserCreated(string Name, string Email);
[EventType]public record DecPassiveUserUpdated(string Name, string Email);
[EventType]public record DecPassiveUserLoggedIn(DateTimeOffset LoginTime);Kotlin does not support this workflow yet.Java does not support this workflow yet.defmodule MyApp.Events.DecPassiveUserCreated do use Chronicle.Events.EventType, id: "dec-passive-user-created"
defstruct [:name, :email]end
defmodule MyApp.Events.DecPassiveUserUpdated do use Chronicle.Events.EventType, id: "dec-passive-user-updated"
defstruct [:name, :email]end
defmodule MyApp.Events.DecPassiveUserLoggedIn do use Chronicle.Events.EventType, id: "dec-passive-user-logged-in"
defstruct [:login_time]endimport { eventType } from '@cratis/chronicle';
@eventType()class DecPassiveUserCreated { name = ''; email = '';}
@eventType()class DecPassiveUserUpdated { name = ''; email = '';}
@eventType()class DecPassiveUserLoggedIn { loginTime = new Date();}How it works
Section titled “How it works”When you call GetInstanceById on a passive projection:
- Chronicle retrieves all relevant events for the specified event source ID
- The projection logic is applied to reconstruct the read model in memory
- The resulting read model is returned without being persisted
- Each call reconstructs the model from scratch, ensuring up-to-date data
When to use passive projections
Section titled “When to use passive projections”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
Performance considerations
Section titled “Performance considerations”- 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