Filter reducers by appended event metadata
A reducer observes all events that match its handler method signatures by default. You can restrict which events a reducer receives by placing filter attributes on the reducer class. Chronicle evaluates these filters before dispatching an event — events that do not match are dropped before they ever reach the reducer.
Filter attributes
Section titled “Filter attributes”| Attribute | Filters by | Matches when |
|---|---|---|
[FilterEventsByTag("...")] | Appended or static event tag | Any filter tag matches any tag on the appended event |
[EventSourceType("...")] | Event source type set at append time | The event source type matches exactly |
[EventStreamType("...")] | Event stream type set at append time | The event stream type matches exactly |
These attributes correlate directly to the metadata you provide when appending an event:
using Cratis.Chronicle.Events;using Cratis.Chronicle.EventSequences;
[EventType]public record ReducersFilteringOrderPlaced(decimal TotalAmount);
public class ReducersFilteringMetadataExampleService(IEventLog eventLog){ public Task PlaceOrder(decimal totalAmount) => eventLog.Append( EventSourceId.New(), new ReducersFilteringOrderPlaced(totalAmount), tags: ["priority"], eventSourceType: "order", eventStreamType: "fulfillment");}Filter by tag
Section titled “Filter by tag”Append an event with a tag and place [FilterEventsByTag] on the reducer to accumulate state only from tagged events.
using Cratis.Chronicle.Events;using Cratis.Chronicle.EventSequences;using Cratis.Chronicle.Reducers;
[EventType]public record ReducersFilteringByTagOrderPlaced(decimal TotalAmount);
public record ReducersFilteringPriorityOrderTotals(int Count, decimal Total);
public class ReducersFilteringByTagOrderService(IEventLog eventLog){ public Task PlacePriorityOrder(decimal totalAmount) => eventLog.Append( EventSourceId.New(), new ReducersFilteringByTagOrderPlaced(totalAmount), tags: ["priority"]);}
[FilterEventsByTag("priority")]public class ReducersFilteringPriorityOrderTotalsReducer : IReducerFor<ReducersFilteringPriorityOrderTotals>{ public ReducersFilteringPriorityOrderTotals Placed(ReducersFilteringByTagOrderPlaced @event, ReducersFilteringPriorityOrderTotals? current, EventContext context) => new((current?.Count ?? 0) + 1, (current?.Total ?? 0m) + @event.TotalAmount);}PriorityOrderTotalsReducer updates only when the appended event carries the priority tag. Orders without that tag do not affect this read model.
Multiple filter tags
Section titled “Multiple filter tags”Multiple [FilterEventsByTag] attributes widen the match. The reducer receives the event if the appended event has any of the configured tags:
using Cratis.Chronicle.Events;using Cratis.Chronicle.Reducers;
[EventType]public record ReducersFilteringMultiTagOrderPlaced(decimal TotalAmount);
public record ReducersFilteringFastTrackOrderTotals(int Count);
[FilterEventsByTag("priority")][FilterEventsByTag("express")]public class ReducersFilteringFastTrackOrderTotalsReducer : IReducerFor<ReducersFilteringFastTrackOrderTotals>{ public ReducersFilteringFastTrackOrderTotals Placed(ReducersFilteringMultiTagOrderPlaced @event, ReducersFilteringFastTrackOrderTotals? current, EventContext context) => new((current?.Count ?? 0) + 1);}Filter by event source type
Section titled “Filter by event source type”Place [EventSourceType] on the reducer to accumulate state only from events appended with a matching eventSourceType:
using Cratis.Chronicle.Events;using Cratis.Chronicle.EventSequences;using Cratis.Chronicle.Reducers;
[EventType]public record ReducersFilteringInvoiceIssued(decimal Amount);
public record ReducersFilteringCustomerInvoiceTotal(decimal Amount);
public class ReducersFilteringInvoicingService(IEventLog eventLog){ public Task IssueCustomerInvoice(decimal amount) => eventLog.Append( EventSourceId.New(), new ReducersFilteringInvoiceIssued(amount), eventSourceType: "customer");}
[EventSourceType("customer")]public class ReducersFilteringCustomerInvoiceTotalReducer : IReducerFor<ReducersFilteringCustomerInvoiceTotal>{ public ReducersFilteringCustomerInvoiceTotal Issued(ReducersFilteringInvoiceIssued @event, ReducersFilteringCustomerInvoiceTotal? current, EventContext context) => new((current?.Amount ?? 0m) + @event.Amount);}If the same event is appended with eventSourceType: "supplier", this reducer does not receive it.
Filter by event stream type
Section titled “Filter by event stream type”Place [EventStreamType] on the reducer to accumulate state only from events appended to a matching stream type:
using Cratis.Chronicle.Events;using Cratis.Chronicle.EventSequences;using Cratis.Chronicle.Reducers;
[EventType]public record ReducersFilteringShipmentSent(decimal ShippingCost);
public record ReducersFilteringShippingTotals(int Count, decimal TotalCost);
public class ReducersFilteringShippingService(IEventLog eventLog){ public Task Send(decimal shippingCost) => eventLog.Append( EventSourceId.New(), new ReducersFilteringShipmentSent(shippingCost), eventStreamType: "shipping");}
[EventStreamType("shipping")]public class ReducersFilteringShippingTotalsReducer : IReducerFor<ReducersFilteringShippingTotals>{ public ReducersFilteringShippingTotals Sent(ReducersFilteringShipmentSent @event, ReducersFilteringShippingTotals? current, EventContext context) => new((current?.Count ?? 0) + 1, (current?.TotalCost ?? 0m) + @event.ShippingCost);}Combine multiple filters
Section titled “Combine multiple filters”You can combine [FilterEventsByTag], [EventSourceType], and [EventStreamType] on the same reducer. Chronicle requires all filter categories to match:
using Cratis.Chronicle.Events;using Cratis.Chronicle.EventSequences;using Cratis.Chronicle.Reducers;
[EventType]public record ReducersFilteringCombineOrderPlaced(decimal TotalAmount);
public record ReducersFilteringPremiumFulfillmentTotals(int Count, decimal Total);
public class ReducersFilteringCombineOrderService(IEventLog eventLog){ public Task PlacePremiumOrder(decimal totalAmount) => eventLog.Append( EventSourceId.New(), new ReducersFilteringCombineOrderPlaced(totalAmount), tags: ["premium"], eventSourceType: "order", eventStreamType: "fulfillment");}
[FilterEventsByTag("premium")][EventSourceType("order")][EventStreamType("fulfillment")]public class ReducersFilteringPremiumFulfillmentTotalsReducer : IReducerFor<ReducersFilteringPremiumFulfillmentTotals>{ public ReducersFilteringPremiumFulfillmentTotals Placed(ReducersFilteringCombineOrderPlaced @event, ReducersFilteringPremiumFulfillmentTotals? current, EventContext context) => new((current?.Count ?? 0) + 1, (current?.Total ?? 0m) + @event.TotalAmount);}The reducer only receives events that match all three: the premium tag, the order event source type, and the fulfillment stream type.
Tagging the reducer vs filtering events
Section titled “Tagging the reducer vs filtering events”[Tag] and [Tags] on a reducer class label the reducer itself for organization and discoverability. They do not filter incoming events:
using Cratis.Chronicle;using Cratis.Chronicle.Events;using Cratis.Chronicle.Reducers;
[EventType]public record ReducersFilteringTagVsFilterOrderPlaced(decimal TotalAmount);
public record ReducersFilteringTagVsFilterTotals(int Count, decimal Total);
// These labels appear on the reducer definition — they do not affect dispatch[Tag("reporting")][Tag("premium")]public class ReducersFilteringLabeledFulfillmentTotalsReducer : IReducerFor<ReducersFilteringTagVsFilterTotals>{ public ReducersFilteringTagVsFilterTotals Placed(ReducersFilteringTagVsFilterOrderPlaced @event, ReducersFilteringTagVsFilterTotals? current, EventContext context) => new((current?.Count ?? 0) + 1, (current?.Total ?? 0m) + @event.TotalAmount);}
// These filter which events are dispatched to the reducer[FilterEventsByTag("premium")][EventSourceType("order")]public class ReducersFilteringFilteredFulfillmentTotalsReducer : IReducerFor<ReducersFilteringTagVsFilterTotals>{ public ReducersFilteringTagVsFilterTotals Placed(ReducersFilteringTagVsFilterOrderPlaced @event, ReducersFilteringTagVsFilterTotals? current, EventContext context) => new((current?.Count ?? 0) + 1, (current?.Total ?? 0m) + @event.TotalAmount);}