Tagging
Tags provide a flexible way to organize, categorize, and identify events and observers (reactors, reducers, projections, read models) within Chronicle. By applying the [Tag] or [Tags] attribute, you can assign one or more tags that describe the purpose, domain, or context of your artifacts.
Note: Both
[Tag]and[Tags]attributes can be used interchangeably. Use whichever feels more natural, or mix them - all tags will be merged together.
Overview
Section titled “Overview”Tags are strings that can be attached to:
- Events - both statically (via attributes) and dynamically (when appending)
- Reactors - for organizing and filtering reactive behaviors
- Reducers - for categorizing state aggregation logic
- Projections - for organizing view models and read models
- Read Models - for categorizing data models
Tags are stored with event metadata and observer definitions, enabling powerful filtering, querying, and organizational capabilities.
Tagging Events
Section titled “Tagging Events”Events can be tagged in two ways: statically using attributes, or dynamically when appending events.
Static Event Tags
Section titled “Static Event Tags”Apply the [Tag] or [Tags] attribute to your event types to assign static tags that will always be associated with that event type:
using Cratis.Chronicle;using Cratis.Chronicle.Events;
[EventType][Tag("analytics", "user-action")]public record TaggingUserLoggedIn(string UserId, DateTimeOffset LoggedInAt);
// [Tags] (plural) is equivalent to [Tag] — use whichever reads more naturally[EventType][Tags("analytics", "user-action")]public record TaggingUserLoggedInAlternate(string UserId, DateTimeOffset LoggedInAt);
// Mixing [Tag] and [Tags] on the same type merges all the tags[EventType][Tag("security")][Tags("audit")]public record TaggingUserPasswordChanged(string UserId, DateTimeOffset ChangedAt);Dynamic Event Tags
Section titled “Dynamic Event Tags”When appending events, you can provide additional tags that will be merged with any static tags:
using Cratis.Chronicle.Events;using Cratis.Chronicle.EventSequences;
public class TaggingUserLoginService(IEventLog eventLog){ public Task RecordLogin(EventSourceId eventSourceId) => // The event will end up with four tags: ["analytics", "user-action", "production", "critical"] eventLog.Append( eventSourceId, new TaggingUserLoggedIn("user123", DateTimeOffset.UtcNow), tags: ["production", "critical"]);}Kotlin does not support this workflow yet.Java does not support this workflow yet.defmodule MyApp.Events.TaggingUserLoggedIn do use Chronicle.Events.EventType, id: "tagging-user-logged-in"
defstruct [:user_id, :logged_in_at]end
defmodule MyApp.TaggingUserLoginService do alias MyApp.Events.TaggingUserLoggedIn
def record_login(event_source_id, user_id) do # Elixir doesn't support static tags on the event type, so this event ends up # with only the two dynamic tags: ["production", "critical"] Chronicle.append( event_source_id, %TaggingUserLoggedIn{user_id: user_id, logged_in_at: DateTime.utc_now()}, tags: ["production", "critical"] ) endendTypeScript does not support this workflow yet.In this example, the event will have four tags: ["analytics", "user-action", "production", "critical"].
Tags in Event Context
Section titled “Tags in Event Context”All tags (both static and dynamic) are available on the EventContext when processing events:
using System.Linq;using Cratis.Chronicle.Events;using Cratis.Chronicle.Reducers;
public record TaggingUserAnalytics(int LoginCount, int CriticalLoginCount);
public class TaggingUserAnalyticsReducer : IReducerFor<TaggingUserAnalytics>{ public TaggingUserAnalytics LoggedIn(TaggingUserLoggedIn @event, TaggingUserAnalytics? current, EventContext context) { var analytics = current ?? new TaggingUserAnalytics(0, 0);
// Access tags from the event context var isCritical = context.Tags.Any(tag => tag.Value == "critical");
return analytics with { LoginCount = analytics.LoginCount + 1, CriticalLoginCount = analytics.CriticalLoginCount + (isCritical ? 1 : 0) }; }}Tagging Observers
Section titled “Tagging Observers”Observers (reactors, reducers, and projections) can be tagged to organize and categorize them:
Single Tag
Section titled “Single Tag”using Cratis.Chronicle;using Cratis.Chronicle.Reactors;
[Tag("Notifications")]public class TaggingOrderConfirmationReactor : IReactor;Multiple Tags (Single Attribute)
Section titled “Multiple Tags (Single Attribute)”You can use either [Tag] or [Tags] (plural) for convenience:
using Cratis.Chronicle;using Cratis.Chronicle.Reactors;
[Tag("Notifications", "Customer", "Email")]public class TaggingCustomerNotificationReactor : IReactor;
// [Tags] (plural) is equivalent — use whichever reads more naturally[Tags("Notifications", "Customer", "Email")]public class TaggingCustomerNotificationReactorAlternate : IReactor;Multiple Tags (Multiple Attributes)
Section titled “Multiple Tags (Multiple Attributes)”using Cratis.Chronicle;using Cratis.Chronicle.Reactors;
[Tag("Integration")][Tag("ExternalAPI")][Tag("Inventory")]public class TaggingInventorySyncReactor : IReactor;Mixed Approach
Section titled “Mixed Approach”You can mix both [Tag] and [Tags] attributes - all tags will be merged:
using Cratis.Chronicle;using Cratis.Chronicle.Reactors;
[Tag("Notifications", "SMS")][Tags("Customer")]public class TaggingSmsNotificationReactor : IReactor;
// Or mix single and multiple attributes the other way around[Tag("Integration")][Tags("ExternalAPI", "Inventory")]public class TaggingInventorySyncReactorMixed : IReactor;Tagging Read Models
Section titled “Tagging Read Models”Read models and projections can also be tagged:
using Cratis.Chronicle;
[Tag("Reporting", "Analytics")]public record TaggingConceptsSalesReport(decimal TotalSales, int OrderCount);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 artifacts: Use tags to group events and observers that serve similar purposes
- Consider hierarchies: Use dot notation for hierarchical tags (e.g.,
"customer.registration","customer.profile")
Common Tagging Patterns
Section titled “Common Tagging Patterns”Here are some common patterns for organizing tags:
By Domain
Section titled “By Domain”using Cratis.Chronicle;
[Tag("Sales")][Tag("Inventory")][Tag("Customer")][Tag("Shipping")]public record TaggingByDomainExample;By Purpose
Section titled “By Purpose”using Cratis.Chronicle;
[Tag("Analytics")][Tag("Reporting")][Tag("Integration")][Tag("Alerting")][Tag("Monitoring")][Tag("Automation")]public record TaggingByPurposeExample;By Integration Type
Section titled “By Integration Type”using Cratis.Chronicle;
[Tag("Notifications")][Tag("ExternalAPI")][Tag("MessageQueue")][Tag("FileSystem")]public record TaggingByIntegrationTypeExample;By Communication Channel
Section titled “By Communication Channel”using Cratis.Chronicle;
[Tag("Email")][Tag("SMS")][Tag("Push")][Tag("Webhook")]public record TaggingByCommunicationChannelExample;By Stakeholder
Section titled “By Stakeholder”using Cratis.Chronicle;
[Tag("Customer")][Tag("Operations")][Tag("Finance")][Tag("Support")][Tag("Executive")]public record TaggingByStakeholderExample;By Environment or Context
Section titled “By Environment or Context”using Cratis.Chronicle.Events;using Cratis.Chronicle.EventSequences;
[EventType]public record TaggingDynamicTagsEventOccurred(string Data);
public class TaggingDynamicTagsService(IEventLog eventLog){ public Task RecordProductionCritical(EventSourceId eventSourceId) => eventLog.Append(eventSourceId, new TaggingDynamicTagsEventOccurred("production issue"), tags: ["production", "critical"]);
public Task RecordDevelopmentTest(EventSourceId eventSourceId) => eventLog.Append(eventSourceId, new TaggingDynamicTagsEventOccurred("test run"), tags: ["development", "testing"]);
public Task RecordBatchMigration(EventSourceId eventSourceId) => eventLog.Append(eventSourceId, new TaggingDynamicTagsEventOccurred("batch migration"), tags: ["migration", "batch-process"]);}Using Tags
Section titled “Using Tags”Tags stored in the event store and observer definitions can be used for:
- Filtering and searching for specific events or observers
- Organizing artifacts in administrative interfaces
- Generating documentation about your system
- Managing deployments by tag
- Monitoring and alerting based on tag groups
- Controlling activation of observers by tag
- Analyzing event patterns and flows
- Creating tag-based subscriptions or filters
Note: The specific querying and filtering capabilities depend on your Chronicle setup and tooling.