Skip to content

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.

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.

Events can be tagged in two ways: statically using attributes, or dynamically when appending events.

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);

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"]);
}

In this example, the event will have four tags: ["analytics", "user-action", "production", "critical"].

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)
};
}
}

Observers (reactors, reducers, and projections) can be tagged to organize and categorize them:

using Cratis.Chronicle;
using Cratis.Chronicle.Reactors;
[Tag("Notifications")]
public class TaggingOrderConfirmationReactor : IReactor;

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;
using Cratis.Chronicle;
using Cratis.Chronicle.Reactors;
[Tag("Integration")]
[Tag("ExternalAPI")]
[Tag("Inventory")]
public class TaggingInventorySyncReactor : IReactor;

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;

Read models and projections can also be tagged:

using Cratis.Chronicle;
[Tag("Reporting", "Analytics")]
public record TaggingConceptsSalesReport(decimal TotalSales, int OrderCount);
  • 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")

Here are some common patterns for organizing tags:

using Cratis.Chronicle;
[Tag("Sales")]
[Tag("Inventory")]
[Tag("Customer")]
[Tag("Shipping")]
public record TaggingByDomainExample;
using Cratis.Chronicle;
[Tag("Analytics")]
[Tag("Reporting")]
[Tag("Integration")]
[Tag("Alerting")]
[Tag("Monitoring")]
[Tag("Automation")]
public record TaggingByPurposeExample;
using Cratis.Chronicle;
[Tag("Notifications")]
[Tag("ExternalAPI")]
[Tag("MessageQueue")]
[Tag("FileSystem")]
public record TaggingByIntegrationTypeExample;
using Cratis.Chronicle;
[Tag("Email")]
[Tag("SMS")]
[Tag("Push")]
[Tag("Webhook")]
public record TaggingByCommunicationChannelExample;
using Cratis.Chronicle;
[Tag("Customer")]
[Tag("Operations")]
[Tag("Finance")]
[Tag("Support")]
[Tag("Executive")]
public record TaggingByStakeholderExample;
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"]);
}

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.