Skip to content

Constant Keys

A constant key allows all events of a given type to accumulate into a single read model instance, regardless of which event source the events originate from. This is useful for global aggregates, system-wide counters, and singleton read models.

Use UsingConstantKey(string value) to specify a fixed key value for a from block. The read model is a normal record or class — the constant key becomes its _id in the underlying store:

using Cratis.Chronicle.Events;
using Cratis.Chronicle.Projections;
[EventType]
public record DecConstantKeyOrderPlaced(decimal Total);
public record DecConstantKeyGlobalCounter(int TotalOrders);
public class DecConstantKeyGlobalCounterProjection : IProjectionFor<DecConstantKeyGlobalCounter>
{
public void Define(IProjectionBuilderFor<DecConstantKeyGlobalCounter> builder) => builder
.From<DecConstantKeyOrderPlaced>(_ => _
.UsingConstantKey("global")
.Count(m => m.TotalOrders));
}

View C# snippet source on GitHub

Every OrderPlaced event, from every event source, updates the single GlobalCounter instance with the key "global".

Constant keys work with counting and arithmetic functions. Keep separate totals for occurrences and currently active items, using count for the former and increment/decrement for the latter:

[EventType]
public record DecConstantKeyUserRegistered(string Name);
[EventType]
public record DecConstantKeyUserLoggedIn;
[EventType]
public record DecConstantKeyUserLoggedOut;
public record DecConstantKeySiteStatistics(
int TotalUsers,
int ActiveSessions);
public class DecConstantKeySiteStatisticsProjection : IProjectionFor<DecConstantKeySiteStatistics>
{
public void Define(IProjectionBuilderFor<DecConstantKeySiteStatistics> builder) => builder
.From<DecConstantKeyUserRegistered>(_ => _
.UsingConstantKey("site")
.Count(m => m.TotalUsers))
.From<DecConstantKeyUserLoggedIn>(_ => _
.UsingConstantKey("site")
.Increment(m => m.ActiveSessions))
.From<DecConstantKeyUserLoggedOut>(_ => _
.UsingConstantKey("site")
.Decrement(m => m.ActiveSessions));
}

View C# snippet source on GitHub

Use UsingConstantParentKey(string value) when working with child collections and you want all events to target the same parent read model:

using Cratis.Chronicle.Events;
using Cratis.Chronicle.Projections;
[EventType]
public record DecConstantKeyUserJoined(string UserId, string UserName);
public record DecConstantKeyTeamMember(string UserId, string Name);
public record DecConstantKeyTeam(IEnumerable<DecConstantKeyTeamMember> Members);
public class DecConstantKeyTeamActivityProjection : IProjectionFor<DecConstantKeyTeam>
{
public void Define(IProjectionBuilderFor<DecConstantKeyTeam> builder) => builder
.Children(m => m.Members, children => children
.IdentifiedBy(e => e.UserId)
.From<DecConstantKeyUserJoined>(_ => _
.UsingConstantParentKey("main-team")
.Set(m => m.Name).To(e => e.UserName)));
}

View C# snippet source on GitHub

StrategyMethodWhen to use
Event source ID(default)Each event stream is one instance
Event propertyUsingKey(e => e.Property)Property on the event identifies instance
Event contextUsingKeyFromContext(c => c.Property)Event context property identifies instance
CompositeUsingCompositeKey<T>(...)Multiple values together form identity
ConstantUsingConstantKey("value")All events update the same instance
using Cratis.Chronicle.Events;
using Cratis.Chronicle.Projections;
[EventType]
public record DecConstantKeyPageViewed(string PageUrl);
[EventType]
public record DecConstantKeyButtonClicked(string ButtonId);
[EventType]
public record DecConstantKeyFormSubmitted(string FormId);
public record DecConstantKeyEngagementMetrics(
int PageViews,
int ButtonClicks,
int FormSubmissions);
public class DecConstantKeyEngagementMetricsProjection : IProjectionFor<DecConstantKeyEngagementMetrics>
{
public void Define(IProjectionBuilderFor<DecConstantKeyEngagementMetrics> builder) => builder
.From<DecConstantKeyPageViewed>(_ => _
.UsingConstantKey("metrics")
.Count(m => m.PageViews))
.From<DecConstantKeyButtonClicked>(_ => _
.UsingConstantKey("metrics")
.Count(m => m.ButtonClicks))
.From<DecConstantKeyFormSubmitted>(_ => _
.UsingConstantKey("metrics")
.Count(m => m.FormSubmissions));
}

View C# snippet source on GitHub

This projection collects engagement events from all users and event sources into a single EngagementMetrics document with the key "metrics".