Tagging Reducers
Tags provide a way to organize and categorize your reducers for better discoverability and management. By applying the [Tag] attribute to your reducer classes, you can assign one or more tags that describe the purpose or domain of the reducer.
[Tag] and [Tags] label the reducer itself. They do not filter which appended events the reducer handles. For event filtering based on appended metadata, see Filter reducers and reactors by tag, Filter reducers and reactors by event source type, and Filter reducers and reactors by event stream type.
Kotlin and Java use repeatable @Tag annotations. TypeScript uses @tag(...) or @tags(...) on
the reducer class; those labels are sent with its observer registration.
For self reducers, place descriptive tags on the read model itself. They remain observer labels, not append-time event tags or delivery filters.
Adding Tags
Section titled “Adding Tags”You can tag reducers in multiple ways:
Single Tag
Section titled “Single Tag”Apply a single tag to your reducer:
using Cratis.Chronicle;using Cratis.Chronicle.Events;using Cratis.Chronicle.Reducers;
[EventType]public record TaggingReducersOrderPlaced(decimal TotalAmount);
public record TaggingReducersOrderAnalytics(int OrderCount, decimal TotalAmount);
[Tag("Analytics")]public class TaggingReducersOrderAnalyticsReducer : IReducerFor<TaggingReducersOrderAnalytics>{ public TaggingReducersOrderAnalytics Placed(TaggingReducersOrderPlaced @event, TaggingReducersOrderAnalytics? current, EventContext context) => new((current?.OrderCount ?? 0) + 1, (current?.TotalAmount ?? 0m) + @event.TotalAmount);}import io.cratis.chronicle.events.EventContextimport io.cratis.chronicle.events.EventTypeimport io.cratis.chronicle.observation.Reducerimport io.cratis.chronicle.observation.Tagimport io.cratis.chronicle.readModels.ReadModel
@EventTypedata class TaggingReducersOrderPlaced(val totalAmount: Double)
@ReadModeldata class TaggingReducersOrderAnalytics(val orderCount: Int = 0, val totalAmount: Double = 0.0)
@Reducer@Tag("Analytics")class TaggingReducersOrderAnalyticsReducer { fun placed(event: TaggingReducersOrderPlaced, current: TaggingReducersOrderAnalytics?, context: EventContext) = TaggingReducersOrderAnalytics((current?.orderCount ?: 0) + 1, (current?.totalAmount ?: 0.0) + event.totalAmount)}import io.cratis.chronicle.events.EventContext;import io.cratis.chronicle.events.EventType;import io.cratis.chronicle.observation.Reducer;import io.cratis.chronicle.observation.Tag;import io.cratis.chronicle.readModels.ReadModel;
@EventTyperecord TaggingReducersOrderPlaced(double totalAmount) {}
@ReadModelrecord TaggingReducersOrderAnalytics(int orderCount, double totalAmount) { TaggingReducersOrderAnalytics() { this(0, 0.0); }}
@Reducer@Tag("Analytics")class TaggingReducersOrderAnalyticsReducer { TaggingReducersOrderAnalytics placed(TaggingReducersOrderPlaced event, TaggingReducersOrderAnalytics current, EventContext context) { int count = current == null ? 0 : current.orderCount(); double total = current == null ? 0.0 : current.totalAmount(); return new TaggingReducersOrderAnalytics(count + 1, total + event.totalAmount()); }}Elixir does not support this workflow yet.import { eventType, reducer, tag } from '@cratis/chronicle';import { field } from '@cratis/fundamentals';
@eventType()class TaggingReducersOrderPlaced { @field(Number) readonly totalAmount: number;
constructor(totalAmount: number) { this.totalAmount = totalAmount; }}
class TaggingReducersOrderAnalytics { orderCount = 0; totalAmount = 0;}
@reducer('', undefined, TaggingReducersOrderAnalytics)@tag('Analytics')class TaggingReducersOrderAnalyticsReducer { taggingReducersOrderPlaced( event: TaggingReducersOrderPlaced, current: TaggingReducersOrderAnalytics | undefined ): TaggingReducersOrderAnalytics { return { orderCount: (current?.orderCount ?? 0) + 1, totalAmount: (current?.totalAmount ?? 0) + event.totalAmount }; }}Multiple Tags (Single Attribute)
Section titled “Multiple Tags (Single Attribute)”Use the params feature to specify multiple tags in a single attribute:
using Cratis.Chronicle;using Cratis.Chronicle.Reducers;
public record TaggingReducersSalesReport(decimal TotalSales);
[Tag("Analytics", "Reporting", "Dashboard")]public class TaggingReducersSalesReportReducer : IReducerFor<TaggingReducersSalesReport>;import io.cratis.chronicle.events.EventTypeimport io.cratis.chronicle.observation.Reducerimport io.cratis.chronicle.observation.Tagimport io.cratis.chronicle.readModels.ReadModel
@ReadModeldata class TaggingReducersSalesReport(val totalSales: Double = 0.0)
// @Tag takes any number of tags in a single attribute@Reducer@Tag("Analytics", "Reporting", "Dashboard")class TaggingReducersSalesReportReducer { fun on(event: TaggingReducersSaleRecorded) = TaggingReducersSalesReport(event.totalSales)}
@EventTypedata class TaggingReducersSaleRecorded(val totalSales: Double = 0.0)import io.cratis.chronicle.events.EventType;import io.cratis.chronicle.observation.Reducer;import io.cratis.chronicle.observation.Tag;import io.cratis.chronicle.readModels.ReadModel;
@ReadModelrecord TaggingReducersSalesReport(double totalSales) { TaggingReducersSalesReport() { this(0.0); }}
// @Tag takes any number of tags in a single attribute@Reducer@Tag({"Analytics", "Reporting", "Dashboard"})class TaggingReducersSalesReportReducer { public TaggingReducersSalesReport on(TaggingReducersSaleRecorded event) { return new TaggingReducersSalesReport(event.totalSales()); }}
@EventTyperecord TaggingReducersSaleRecorded(double totalSales) {}Elixir does not support this workflow yet.import { reducer, tag } from '@cratis/chronicle';
class TaggingReducersSalesReport { totalSales = 0;}
@reducer('', undefined, TaggingReducersSalesReport)@tag('Analytics', 'Reporting', 'Dashboard')class TaggingReducersSalesReportReducer {}Multiple Tags (Multiple Attributes)
Section titled “Multiple Tags (Multiple Attributes)”Apply multiple [Tag] attributes:
using Cratis.Chronicle;using Cratis.Chronicle.Reducers;
public record TaggingReducersComplianceReport(string Status);
[Tag("Analytics")][Tag("Compliance")][Tag("Auditing")]public class TaggingReducersComplianceReportReducer : IReducerFor<TaggingReducersComplianceReport>;import io.cratis.chronicle.events.EventTypeimport io.cratis.chronicle.observation.Reducerimport io.cratis.chronicle.observation.Tagimport io.cratis.chronicle.readModels.ReadModel
@ReadModeldata class TaggingReducersComplianceReport(val status: String = "")
@Reducer@Tag("Analytics")@Tag("Compliance")@Tag("Auditing")class TaggingReducersComplianceReportReducer { fun on(event: TaggingReducersAuditEntryRecorded) = TaggingReducersComplianceReport(event.status)}
@EventTypedata class TaggingReducersAuditEntryRecorded(val status: String = "")import io.cratis.chronicle.events.EventType;import io.cratis.chronicle.observation.Reducer;import io.cratis.chronicle.observation.Tag;import io.cratis.chronicle.readModels.ReadModel;
@ReadModelrecord TaggingReducersComplianceReport(String status) { TaggingReducersComplianceReport() { this(""); }}
@Reducer@Tag("Analytics")@Tag("Compliance")@Tag("Auditing")class TaggingReducersComplianceReportReducer { public TaggingReducersComplianceReport on(TaggingReducersAuditEntryRecorded event) { return new TaggingReducersComplianceReport(event.status()); }}
@EventTyperecord TaggingReducersAuditEntryRecorded(String status) {}Elixir does not support this workflow yet.import { reducer, tag } from '@cratis/chronicle';
class TaggingReducersComplianceReport { status = '';}
@reducer('', undefined, TaggingReducersComplianceReport)@tag('Analytics')@tag('Compliance')@tag('Auditing')class TaggingReducersComplianceReportReducer {}Mixed Approach
Section titled “Mixed Approach”Combine both approaches:
using Cratis.Chronicle;using Cratis.Chronicle.Reducers;
public record TaggingReducersExecutiveDashboard(int MetricCount);
[Tag("Analytics", "Reporting")][Tag("Executive")]public class TaggingReducersExecutiveDashboardReducer : IReducerFor<TaggingReducersExecutiveDashboard>;import io.cratis.chronicle.events.EventTypeimport io.cratis.chronicle.observation.Reducerimport io.cratis.chronicle.observation.Tagimport io.cratis.chronicle.readModels.ReadModel
@ReadModeldata class TaggingReducersExecutiveDashboard(val metricCount: Int = 0)
@Reducer@Tag("Analytics", "Reporting")@Tag("Executive")class TaggingReducersExecutiveDashboardReducer { fun on(event: TaggingReducersKpiRecorded) = TaggingReducersExecutiveDashboard(event.metricCount)}
@EventTypedata class TaggingReducersKpiRecorded(val metricCount: Int = 0)import io.cratis.chronicle.events.EventType;import io.cratis.chronicle.observation.Reducer;import io.cratis.chronicle.observation.Tag;import io.cratis.chronicle.readModels.ReadModel;
@ReadModelrecord TaggingReducersExecutiveDashboard(int metricCount) { TaggingReducersExecutiveDashboard() { this(0); }}
@Reducer@Tag({"Analytics", "Reporting"})@Tag("Executive")class TaggingReducersExecutiveDashboardReducer { public TaggingReducersExecutiveDashboard on(TaggingReducersKpiRecorded event) { return new TaggingReducersExecutiveDashboard(event.metricCount()); }}
@EventTyperecord TaggingReducersKpiRecorded(int metricCount) {}Elixir does not support this workflow yet.import { reducer, tag } from '@cratis/chronicle';
class TaggingReducersExecutiveDashboard { metricCount = 0;}
@reducer('', undefined, TaggingReducersExecutiveDashboard)@tag('Analytics', 'Reporting')@tag('Executive')class TaggingReducersExecutiveDashboardReducer {}Best Practices
Section titled “Best Practices”- Use meaningful names: Choose tag names that clearly describe the purpose or domain
- Be consistent: Establish tag naming conventions across your organization
- Don’t over-tag: Apply only relevant tags; too many can reduce their usefulness
- Group related reducers: Use tags to group reducers that serve similar purposes
Common Tag Examples
Section titled “Common Tag Examples”Here are some common patterns for tagging reducers:
using Cratis.Chronicle;using Cratis.Chronicle.Reducers;
public record TaggingReducersCategoryExamples(Guid Id);
// By domain[Tag("Sales", "Inventory", "Customer")]// By purpose[Tag("Analytics", "Reporting", "Dashboard", "Auditing")]// By stakeholder[Tag("Executive", "Operations", "Finance")]// By data type[Tag("Aggregates", "Summaries", "Metrics")]public class TaggingReducersCategoryExamplesReducer : IReducerFor<TaggingReducersCategoryExamples>;import io.cratis.chronicle.events.EventTypeimport io.cratis.chronicle.observation.Reducerimport io.cratis.chronicle.observation.Tagimport io.cratis.chronicle.readModels.ReadModelimport java.util.UUID
@ReadModeldata class TaggingReducersCategoryExamples(val id: UUID = UUID(0, 0))
// By domain@Tag("Sales", "Inventory", "Customer")// By purpose@Tag("Analytics", "Reporting", "Dashboard", "Auditing")// By stakeholder@Tag("Executive", "Operations", "Finance")// By data type@Tag("Aggregates", "Summaries", "Metrics")@Reducerclass TaggingReducersCategoryExamplesReducer { fun on(event: TaggingReducersCategoryOrderPlaced) = TaggingReducersCategoryExamples(event.id)}
@EventTypedata class TaggingReducersCategoryOrderPlaced(val id: UUID = UUID(0, 0))import io.cratis.chronicle.events.EventType;import io.cratis.chronicle.observation.Reducer;import io.cratis.chronicle.observation.Tag;import io.cratis.chronicle.readModels.ReadModel;
import java.util.UUID;
@ReadModelrecord TaggingReducersCategoryExamples(UUID id) { TaggingReducersCategoryExamples() { this(new UUID(0, 0)); }}
// By domain@Tag({"Sales", "Inventory", "Customer"})// By purpose@Tag({"Analytics", "Reporting", "Dashboard", "Auditing"})// By stakeholder@Tag({"Executive", "Operations", "Finance"})// By data type@Tag({"Aggregates", "Summaries", "Metrics"})@Reducerclass TaggingReducersCategoryExamplesReducer { public TaggingReducersCategoryExamples on(TaggingReducersCategoryOrderPlaced event) { return new TaggingReducersCategoryExamples(event.id()); }}
@EventTyperecord TaggingReducersCategoryOrderPlaced(UUID id) {}Elixir does not support this workflow yet.import { reducer, tag } from '@cratis/chronicle';
class TaggingReducersCategoryExamples { id = '';}
@reducer('', undefined, TaggingReducersCategoryExamples)// By domain@tag('Sales', 'Inventory', 'Customer')// By purpose@tag('Analytics', 'Reporting', 'Dashboard', 'Auditing')// By stakeholder@tag('Executive', 'Operations', 'Finance')// By data type@tag('Aggregates', 'Summaries', 'Metrics')class TaggingReducersCategoryExamplesReducer {}Querying by Tag
Section titled “Querying by Tag”Tags stored in the event store definition can be used for:
- Filtering and searching for specific reducers
- Organizing reducers in administrative interfaces
- Generating documentation
- Managing reducer deployments by tag
Note: The specific querying capabilities depend on your Chronicle setup and tooling.
See Also
Section titled “See Also”- Tagging - Comprehensive guide to tagging in Chronicle
- Reactors Tagging - Tagging reactors