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.
Events can also carry named tags: a name paired with a value, such as import-batch = 4711. Use them for runtime identifiers you want to find events by later.
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);Kotlin does not support this workflow yet.Java does not support this workflow yet.defmodule MyApp.Events.TaggingStaticUserLoggedIn do use Chronicle.Events.EventType, id: "tagging-static-user-logged-in", tags: ["analytics", "user-action"]
defstruct [:user_id, :logged_in_at]end
# There is one `tags:` option: list every tag the event type carries in it.defmodule MyApp.Events.TaggingStaticUserPasswordChanged do use Chronicle.Events.EventType, id: "tagging-static-user-password-changed", tags: ["security", "audit"]
defstruct [:user_id, :changed_at]endimport { eventType, tag, tags } from '@cratis/chronicle';import { field } from '@cratis/fundamentals';
@eventType()@tag('analytics', 'user-action')class TaggingUserLoggedIn { @field(String) readonly userId: string; @field(Date) readonly loggedInAt: Date;
constructor(userId: string, loggedInAt: Date) { this.userId = userId; this.loggedInAt = loggedInAt; }}
// @tags() (plural) is equivalent to @tag() — use whichever reads more naturally@eventType()@tags('analytics', 'user-action')class TaggingUserLoggedInAlternate { @field(String) readonly userId: string; @field(Date) readonly loggedInAt: Date;
constructor(userId: string, loggedInAt: Date) { this.userId = userId; this.loggedInAt = loggedInAt; }}
// Mixing @tag() and @tags() on the same type merges all the tags@eventType()@tag('security')@tags('audit')class TaggingUserPasswordChanged { @field(String) readonly userId: string; @field(Date) readonly changedAt: Date;
constructor(userId: string, changedAt: Date) { this.userId = userId; this.changedAt = 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"]);}import io.cratis.chronicle.eventSequences.AppendOptionsimport io.cratis.chronicle.eventSequences.IEventLogimport io.cratis.chronicle.events.EventType
@EventTypedata class TaggingUserLoggedIn(val userId: String)
class TaggingUserLoginService(private val eventLog: IEventLog) { suspend fun recordLogin(eventSourceId: String) { eventLog.append( eventSourceId, TaggingUserLoggedIn("user123"), AppendOptions(tags = listOf("production", "critical")) ) }}import io.cratis.chronicle.eventSequences.IEventLog;import io.cratis.chronicle.events.EventType;import io.cratis.chronicle.java.AppendOptionsBuilder;import io.cratis.chronicle.java.EventLogJavaBridge;
@EventTyperecord TaggingUserLoggedIn(String userId) {}
class TaggingUserLoginService { private final IEventLog eventLog;
TaggingUserLoginService(IEventLog eventLog) { this.eventLog = eventLog; }
void recordLogin(String eventSourceId) { EventLogJavaBridge.append( eventLog, eventSourceId, new TaggingUserLoggedIn("user123"), new AppendOptionsBuilder().tag("production").tag("critical").build()); }}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 # This event type declares no static tags, so the event carries 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"] ) endendimport { IEventStore } from '@cratis/chronicle';
class TaggingUserLoginService { constructor(private readonly store: IEventStore) {}
// The event will end up with four tags: ['analytics', 'user-action', 'production', 'critical'] async recordLogin(eventSourceId: string): Promise<void> { await this.store.eventLog.append( eventSourceId, new TaggingUserLoggedIn('user123', new Date()), { tags: ['production', 'critical'] }); }}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) }; }}import io.cratis.chronicle.events.EventContextimport io.cratis.chronicle.observation.Reducerimport io.cratis.chronicle.readModels.ReadModel
@ReadModeldata class TaggingUserAnalytics(val loginCount: Int = 0, val criticalLoginCount: Int = 0)
@Reducerclass TaggingUserAnalyticsReducer { fun loggedIn( event: TaggingUserLoggedIn, current: TaggingUserAnalytics?, context: EventContext ): TaggingUserAnalytics { val analytics = current ?: TaggingUserAnalytics()
// Tags the event was appended with are available on the context. val isCritical = context.tags.contains("critical")
return analytics.copy( loginCount = analytics.loginCount + 1, criticalLoginCount = analytics.criticalLoginCount + if (isCritical) 1 else 0 ) }}import io.cratis.chronicle.events.EventContext;import io.cratis.chronicle.observation.Reducer;import io.cratis.chronicle.readModels.ReadModel;
@ReadModelrecord TaggingUserAnalytics(int loginCount, int criticalLoginCount) { static TaggingUserAnalytics initial() { return new TaggingUserAnalytics(0, 0); }}
@Reducerclass TaggingUserAnalyticsReducer { TaggingUserAnalytics loggedIn(TaggingUserLoggedIn event, TaggingUserAnalytics current, EventContext context) { TaggingUserAnalytics analytics = current != null ? current : TaggingUserAnalytics.initial();
// Tags the event was appended with are available on the context. boolean isCritical = context.getTags().contains("critical");
return new TaggingUserAnalytics( analytics.loginCount() + 1, analytics.criticalLoginCount() + (isCritical ? 1 : 0)); }}defmodule MyApp.ReadModels.TaggingUserAnalytics do use Chronicle.ReadModels.ReadModel
defstruct id: nil, login_count: 0, critical_login_count: 0end
defmodule MyApp.Reducers.TaggingUserAnalyticsReducer do use Chronicle.Reducers.Reducer, model: MyApp.ReadModels.TaggingUserAnalytics
alias MyApp.Events.TaggingUserLoggedIn alias MyApp.ReadModels.TaggingUserAnalytics
@handles TaggingUserLoggedIn
@impl true def reduce(%TaggingUserLoggedIn{}, model, context) do analytics = model || %TaggingUserAnalytics{id: context.event_source_id}
# Access tags from the event context critical? = "critical" in context.tags
%{ analytics | login_count: analytics.login_count + 1, critical_login_count: analytics.critical_login_count + if(critical?, do: 1, else: 0) } endendimport { EventContext, reducer } from '@cratis/chronicle';
class TaggingUserAnalytics { loginCount = 0; criticalLoginCount = 0;}
@reducer('', undefined, TaggingUserAnalytics)class TaggingUserAnalyticsReducer { taggingUserLoggedIn( event: TaggingUserLoggedIn, current: TaggingUserAnalytics | undefined, context: EventContext ): TaggingUserAnalytics { const analytics = current ?? new TaggingUserAnalytics();
// Access tags from the event context const isCritical = context.tags.some(tag => tag.value === 'critical');
return { loginCount: analytics.loginCount + 1, criticalLoginCount: analytics.criticalLoginCount + (isCritical ? 1 : 0) }; }}Named tags
Section titled “Named tags”A plain tag says what kind of event you have: "checkout", "audit". Sometimes you also need to record which import batch, workflow run, or checkout session produced an event, and find those events again later. Packing that into a string such as "import-batch:4711" gives you a tag you can’t query by name, and it breaks as soon as a value contains the separator. A named tag keeps the name and the value apart.
Appending named tags
Section titled “Appending named tags”You supply named tags with AppendWithNamedTags (or AppendManyWithNamedTags for a batch). Unlike plain tags, there is no attribute that declares them on an event type.
using Cratis.Chronicle;using Cratis.Chronicle.Events;using Cratis.Chronicle.EventSequences;
[EventType][Tag("catalog")]public record TaggingCatalogItemImported(string Sku, string Title);
public class TaggingCatalogImportService(IEventLog eventLog){ public Task<AppendResult> Import(EventSourceId itemId, string sku, string title, string batchId) => // Plain tags: ["catalog", "import"] // Named tags: import-batch = <batchId>, source-system = erp eventLog.AppendWithNamedTags( itemId, new TaggingCatalogItemImported(sku, title), namedTags: [new NamedTag("import-batch", batchId), new NamedTag("source-system", "erp")], tags: ["import"]);}import { eventType, IEventStore, NamedTag, tag } from '@cratis/chronicle';import { field } from '@cratis/fundamentals';
@eventType()@tag('catalog')class TaggingCatalogItemImported { @field(String) readonly sku: string; @field(String) readonly title: string;
constructor(sku: string, title: string) { this.sku = sku; this.title = title; }}
class TaggingCatalogImportService { constructor(private readonly store: IEventStore) {}
// Plain tags: ['catalog', 'import'] // Named tags: import-batch = <batchId>, source-system = erp async import(itemId: string, sku: string, title: string, batchId: string): Promise<void> { await this.store.eventLog.append( itemId, new TaggingCatalogItemImported(sku, title), { namedTags: [new NamedTag('import-batch', batchId), new NamedTag('source-system', 'erp')], tags: ['import'] }); }}Named tags and plain tags are stored separately. The [Tag] attribute and the tags values above still produce plain tags, and a named tag never appears in the plain tag list. Appending with Tags covers batch appends and how per-event and call-level named tags combine.
Names and values
Section titled “Names and values”- Matching is exact. Names and values compare ordinally and case-sensitively, so
Import-Batchandimport-batchare two different names. - Values are opaque. Chronicle never parses a value. It can contain
:,.,,or any other character, and it can be empty. - Names can’t be blank. Chronicle rejects a named tag whose name is empty or whitespace.
- One name, several values. The same name with different values gives you separate tags. An exact duplicate of a name and value pair is kept once.
Reading named tags
Section titled “Reading named tags”Named tags come back on the EventContext next to the plain tags:
using System.Linq;using Cratis.Chronicle.Events;using Cratis.Chronicle.Reactors;
public class TaggingCatalogImportTracker : IReactor{ public Task Imported(TaggingCatalogItemImported @event, EventContext context) { // Names and values compare exactly (ordinal, case-sensitive). // A name can carry more than one value, so read every match. var batchIds = context.NamedTags .Where(tag => tag.Name.Value == "import-batch") .Select(tag => tag.Value) .ToArray();
Console.WriteLine($"{@event.Sku} imported in batch(es) {string.Join(", ", batchIds)}"); return Task.CompletedTask; }}import { EventContext, reactor } from '@cratis/chronicle';
@reactor()class TaggingCatalogImportTracker { taggingCatalogItemImported(event: TaggingCatalogItemImported, context: EventContext): void { // Names and values compare exactly (case-sensitive). // A name can carry more than one value, so read every match. const batchIds = (context.namedTags ?? []) .filter(tag => tag.name === 'import-batch') .map(tag => tag.value);
console.log(`${event.sku} imported in batch(es) ${batchIds.join(', ')}`); }}Querying by named tag
Section titled “Querying by named tag”The Chronicle server’s event sequence service answers named-tag queries through two operations: QueryEventsWithNamedTags returns a page of events, and SequenceHistogramWithNamedTags returns event counts over time. Each criterion takes one of three forms:
| Criterion | Matches events that carry |
|---|---|
| Name only | The name, with any value |
| Name and a value | Exactly that name and value |
| Name and a set of values | The name with any of those values |
An event matches when it satisfies any one of the criteria. The named-tag criteria then combine with every other filter on the query: event source, event source type, event stream type, correlation ID, event types, plain tags, and the occurred date range. A named-tag query needs at least one criterion, and a criterion with an empty set of values is rejected. Neither case turns into a query that returns everything.
Before you use named tags
Section titled “Before you use named tags”MongoDB, SQL (SQLite, PostgreSQL, and SQL Server), and in-memory storage support named-tag appends and queries. SQL stores them in a companion table named __cratis_named_tags in each event-store namespace database. That name is reserved for Chronicle. If a conflicting table already exists, every event-sequence operation in that namespace fails with NamedTagsTableCollision on first access (migration is lazy) and continues to fail until the conflicting table is renamed.
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;import io.cratis.chronicle.events.EventTypeimport io.cratis.chronicle.observation.Reactorimport io.cratis.chronicle.observation.Tag
@Tag("Notifications")@Reactorclass TaggingOrderConfirmationReactor { fun on(event: TaggingOrderConfirmed) { }}
@EventTypedata class TaggingOrderConfirmed(val id: String = "")import io.cratis.chronicle.events.EventType;import io.cratis.chronicle.observation.Reactor;import io.cratis.chronicle.observation.Tag;
@Tag("Notifications")@Reactorclass TaggingOrderConfirmationReactor { void on(TaggingOrderConfirmed event) { }}
@EventTyperecord TaggingOrderConfirmed(String id) {}Elixir does not support this workflow yet.import { reactor, tag } from '@cratis/chronicle';
@reactor()@tag('Notifications')class TaggingOrderConfirmationReactor {}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;import io.cratis.chronicle.events.EventTypeimport io.cratis.chronicle.observation.Reactorimport io.cratis.chronicle.observation.Tag
@Tag("Notifications", "Customer", "Email")@Reactorclass TaggingCustomerNotificationReactor { fun on(event: TaggingCustomerRegistered) { }}
@EventTypedata class TaggingCustomerRegistered(val id: String = "")import io.cratis.chronicle.events.EventType;import io.cratis.chronicle.observation.Reactor;import io.cratis.chronicle.observation.Tag;
@Tag({"Notifications", "Customer", "Email"})@Reactorclass TaggingCustomerNotificationReactor { void on(TaggingCustomerRegistered event) { }}
@EventTyperecord TaggingCustomerRegistered(String id) {}Elixir does not support this workflow yet.import { reactor, tag, tags } from '@cratis/chronicle';
@reactor()@tag('Notifications', 'Customer', 'Email')class TaggingCustomerNotificationReactor {}
// @tags() (plural) is equivalent — use whichever reads more naturally@reactor()@tags('Notifications', 'Customer', 'Email')class TaggingCustomerNotificationReactorAlternate {}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;import io.cratis.chronicle.events.EventTypeimport io.cratis.chronicle.observation.Reactorimport io.cratis.chronicle.observation.Tag
@Tag("Integration")@Tag("ExternalAPI")@Tag("Inventory")@Reactorclass TaggingInventorySyncReactor { fun on(event: TaggingInventoryAdjusted) { }}
@EventTypedata class TaggingInventoryAdjusted(val id: String = "")import io.cratis.chronicle.events.EventType;import io.cratis.chronicle.observation.Reactor;import io.cratis.chronicle.observation.Tag;
@Tag("Integration")@Tag("ExternalAPI")@Tag("Inventory")@Reactorclass TaggingInventorySyncReactor { void on(TaggingInventoryAdjusted event) { }}
@EventTyperecord TaggingInventoryAdjusted(String id) {}Elixir does not support this workflow yet.import { reactor, tag } from '@cratis/chronicle';
@reactor()@tag('Integration')@tag('ExternalAPI')@tag('Inventory')class TaggingInventorySyncReactor {}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;import io.cratis.chronicle.events.EventTypeimport io.cratis.chronicle.observation.Reactorimport io.cratis.chronicle.observation.Tag
// Kotlin exposes only @Tag (repeatable) - combine a multi-value use with a stacked one however reads best@Tag("Notifications", "SMS")@Tag("Customer")@Reactorclass TaggingSmsNotificationReactor { fun on(event: TaggingSmsRequested) { }}
@EventTypedata class TaggingSmsRequested(val id: String = "")import io.cratis.chronicle.events.EventType;import io.cratis.chronicle.observation.Reactor;import io.cratis.chronicle.observation.Tag;
// Java exposes only @Tag (repeatable) - combine a multi-value use with a stacked one however reads best@Tag({"Notifications", "SMS"})@Tag("Customer")@Reactorclass TaggingSmsNotificationReactor { void on(TaggingSmsRequested event) { }}
@EventTyperecord TaggingSmsRequested(String id) {}Elixir does not support this workflow yet.import { reactor, tag, tags } from '@cratis/chronicle';
@reactor()@tag('Notifications', 'SMS')@tags('Customer')class TaggingSmsNotificationReactor {}
// Or mix single and multiple attributes the other way around@reactor()@tag('Integration')@tags('ExternalAPI', 'Inventory')class TaggingInventorySyncReactorMixed {}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);Kotlin does not support this workflow yet.Java does not support this workflow yet.Elixir does not support this workflow yet.import { tag } from '@cratis/chronicle';import { field } from '@cratis/fundamentals';
@tag('Reporting', 'Analytics')class TaggingConceptsSalesReport { @field(Number) readonly totalSales: number; @field(Number) readonly orderCount: number;
constructor(totalSales: number, orderCount: number) { this.totalSales = totalSales; this.orderCount = 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 fixed, hierarchical categories (e.g.,
"customer.registration","customer.profile") - Don’t encode runtime values in plain tags: A batch number, run ID, or session ID belongs in a named tag, not in a string such as
"batch:4711"or"batch.4711"
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;import io.cratis.chronicle.events.EventTypeimport io.cratis.chronicle.observation.Reactorimport io.cratis.chronicle.observation.Tag
@Tag("Sales")@Tag("Inventory")@Tag("Customer")@Tag("Shipping")@Reactorclass TaggingByDomainExample { fun on(event: TaggingByDomainOrderPlaced) { }}
@EventTypedata class TaggingByDomainOrderPlaced(val id: String = "")import io.cratis.chronicle.events.EventType;import io.cratis.chronicle.observation.Reactor;import io.cratis.chronicle.observation.Tag;
@Tag("Sales")@Tag("Inventory")@Tag("Customer")@Tag("Shipping")@Reactorclass TaggingByDomainExample { void on(TaggingByDomainOrderPlaced event) { }}
@EventTyperecord TaggingByDomainOrderPlaced(String id) {}defmodule MyApp.Events.TaggingByDomainExample do use Chronicle.Events.EventType, id: "tagging-by-domain-example", tags: ["Sales", "Inventory", "Customer", "Shipping"]
defstruct [:data]endimport { tag } from '@cratis/chronicle';
@tag('Sales')@tag('Inventory')@tag('Customer')@tag('Shipping')class 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;import io.cratis.chronicle.events.EventTypeimport io.cratis.chronicle.observation.Reactorimport io.cratis.chronicle.observation.Tag
@Tag("Analytics")@Tag("Reporting")@Tag("Integration")@Tag("Alerting")@Tag("Monitoring")@Tag("Automation")@Reactorclass TaggingByPurposeExample { fun on(event: TaggingByPurposeCustomerRegistered) { }}
@EventTypedata class TaggingByPurposeCustomerRegistered(val id: String = "")import io.cratis.chronicle.events.EventType;import io.cratis.chronicle.observation.Reactor;import io.cratis.chronicle.observation.Tag;
@Tag("Analytics")@Tag("Reporting")@Tag("Integration")@Tag("Alerting")@Tag("Monitoring")@Tag("Automation")@Reactorclass TaggingByPurposeExample { void on(TaggingByPurposeCustomerRegistered event) { }}
@EventTyperecord TaggingByPurposeCustomerRegistered(String id) {}defmodule MyApp.Events.TaggingByPurposeExample do use Chronicle.Events.EventType, id: "tagging-by-purpose-example", tags: ["Analytics", "Reporting", "Integration", "Alerting", "Monitoring", "Automation"]
defstruct [:data]endimport { tag } from '@cratis/chronicle';
@tag('Analytics')@tag('Reporting')@tag('Integration')@tag('Alerting')@tag('Monitoring')@tag('Automation')class TaggingByPurposeExample {}By Integration Type
Section titled “By Integration Type”using Cratis.Chronicle;
[Tag("Notifications")][Tag("ExternalAPI")][Tag("MessageQueue")][Tag("FileSystem")]public record TaggingByIntegrationTypeExample;import io.cratis.chronicle.events.EventTypeimport io.cratis.chronicle.observation.Reactorimport io.cratis.chronicle.observation.Tag
@Tag("Notifications")@Tag("ExternalAPI")@Tag("MessageQueue")@Tag("FileSystem")@Reactorclass TaggingByIntegrationTypeExample { fun on(event: TaggingByIntegrationTypeRecordSynced) { }}
@EventTypedata class TaggingByIntegrationTypeRecordSynced(val id: String = "")import io.cratis.chronicle.events.EventType;import io.cratis.chronicle.observation.Reactor;import io.cratis.chronicle.observation.Tag;
@Tag("Notifications")@Tag("ExternalAPI")@Tag("MessageQueue")@Tag("FileSystem")@Reactorclass TaggingByIntegrationTypeExample { void on(TaggingByIntegrationTypeRecordSynced event) { }}
@EventTyperecord TaggingByIntegrationTypeRecordSynced(String id) {}defmodule MyApp.Events.TaggingByIntegrationTypeExample do use Chronicle.Events.EventType, id: "tagging-by-integration-type-example", tags: ["Notifications", "ExternalAPI", "MessageQueue", "FileSystem"]
defstruct [:data]endimport { tag } from '@cratis/chronicle';
@tag('Notifications')@tag('ExternalAPI')@tag('MessageQueue')@tag('FileSystem')class TaggingByIntegrationTypeExample {}By Communication Channel
Section titled “By Communication Channel”using Cratis.Chronicle;
[Tag("Email")][Tag("SMS")][Tag("Push")][Tag("Webhook")]public record TaggingByCommunicationChannelExample;import io.cratis.chronicle.events.EventTypeimport io.cratis.chronicle.observation.Reactorimport io.cratis.chronicle.observation.Tag
@Tag("Email")@Tag("SMS")@Tag("Push")@Tag("Webhook")@Reactorclass TaggingByCommunicationChannelExample { fun on(event: TaggingByCommunicationChannelMessageSent) { }}
@EventTypedata class TaggingByCommunicationChannelMessageSent(val id: String = "")import io.cratis.chronicle.events.EventType;import io.cratis.chronicle.observation.Reactor;import io.cratis.chronicle.observation.Tag;
@Tag("Email")@Tag("SMS")@Tag("Push")@Tag("Webhook")@Reactorclass TaggingByCommunicationChannelExample { void on(TaggingByCommunicationChannelMessageSent event) { }}
@EventTyperecord TaggingByCommunicationChannelMessageSent(String id) {}defmodule MyApp.Events.TaggingByCommunicationChannelExample do use Chronicle.Events.EventType, id: "tagging-by-communication-channel-example", tags: ["Email", "SMS", "Push", "Webhook"]
defstruct [:data]endimport { tag } from '@cratis/chronicle';
@tag('Email')@tag('SMS')@tag('Push')@tag('Webhook')class TaggingByCommunicationChannelExample {}By Stakeholder
Section titled “By Stakeholder”using Cratis.Chronicle;
[Tag("Customer")][Tag("Operations")][Tag("Finance")][Tag("Support")][Tag("Executive")]public record TaggingByStakeholderExample;import io.cratis.chronicle.events.EventTypeimport io.cratis.chronicle.observation.Reactorimport io.cratis.chronicle.observation.Tag
@Tag("Customer")@Tag("Operations")@Tag("Finance")@Tag("Support")@Tag("Executive")@Reactorclass TaggingByStakeholderExample { fun on(event: TaggingByStakeholderReportRequested) { }}
@EventTypedata class TaggingByStakeholderReportRequested(val id: String = "")import io.cratis.chronicle.events.EventType;import io.cratis.chronicle.observation.Reactor;import io.cratis.chronicle.observation.Tag;
@Tag("Customer")@Tag("Operations")@Tag("Finance")@Tag("Support")@Tag("Executive")@Reactorclass TaggingByStakeholderExample { void on(TaggingByStakeholderReportRequested event) { }}
@EventTyperecord TaggingByStakeholderReportRequested(String id) {}defmodule MyApp.Events.TaggingByStakeholderExample do use Chronicle.Events.EventType, id: "tagging-by-stakeholder-example", tags: ["Customer", "Operations", "Finance", "Support", "Executive"]
defstruct [:data]endimport { tag } from '@cratis/chronicle';
@tag('Customer')@tag('Operations')@tag('Finance')@tag('Support')@tag('Executive')class 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"]);}import io.cratis.chronicle.eventSequences.AppendOptionsimport io.cratis.chronicle.eventSequences.IEventLogimport io.cratis.chronicle.events.EventType
@EventTypedata class TaggingDynamicTagsEventOccurred(val data: String)
class TaggingDynamicTagsService(private val eventLog: IEventLog) { suspend fun recordProductionCritical(eventSourceId: String) = eventLog.append( eventSourceId, TaggingDynamicTagsEventOccurred("production issue"), AppendOptions(tags = listOf("production", "critical")) )
suspend fun recordDevelopmentTest(eventSourceId: String) = eventLog.append( eventSourceId, TaggingDynamicTagsEventOccurred("test run"), AppendOptions(tags = listOf("development", "testing")) )
suspend fun recordBatchMigration(eventSourceId: String) = eventLog.append( eventSourceId, TaggingDynamicTagsEventOccurred("batch migration"), AppendOptions(tags = listOf("migration", "batch-process")) )}import io.cratis.chronicle.eventSequences.IEventLog;import io.cratis.chronicle.events.EventType;
import io.cratis.chronicle.java.AppendOptionsBuilder;import io.cratis.chronicle.java.EventLogJavaBridge;
@EventTyperecord TaggingDynamicTagsEventOccurred(String data) {}
class TaggingDynamicTagsService { private final IEventLog eventLog;
TaggingDynamicTagsService(IEventLog eventLog) { this.eventLog = eventLog; }
void recordProductionCritical(String eventSourceId) { EventLogJavaBridge.append( eventLog, eventSourceId, new TaggingDynamicTagsEventOccurred("production issue"), new AppendOptionsBuilder().tag("production").tag("critical").build()); }
void recordDevelopmentTest(String eventSourceId) { EventLogJavaBridge.append( eventLog, eventSourceId, new TaggingDynamicTagsEventOccurred("test run"), new AppendOptionsBuilder().tag("development").tag("testing").build()); }
void recordBatchMigration(String eventSourceId) { EventLogJavaBridge.append( eventLog, eventSourceId, new TaggingDynamicTagsEventOccurred("batch migration"), new AppendOptionsBuilder().tag("migration").tag("batch-process").build()); }}defmodule MyApp.Events.TaggingDynamicPatternsEventOccurred do use Chronicle.Events.EventType, id: "tagging-dynamic-patterns-event-occurred"
defstruct [:data]end
defmodule MyApp.TaggingDynamicPatternsService do alias MyApp.Events.TaggingDynamicPatternsEventOccurred
def record_production_critical(event_source_id) do Chronicle.append( event_source_id, %TaggingDynamicPatternsEventOccurred{data: "production issue"}, tags: ["production", "critical"] ) end
def record_development_test(event_source_id) do Chronicle.append( event_source_id, %TaggingDynamicPatternsEventOccurred{data: "test run"}, tags: ["development", "testing"] ) end
def record_batch_migration(event_source_id) do Chronicle.append( event_source_id, %TaggingDynamicPatternsEventOccurred{data: "batch migration"}, tags: ["migration", "batch-process"] ) endendimport { eventType, IEventStore } from '@cratis/chronicle';import { field } from '@cratis/fundamentals';
@eventType()class TaggingDynamicTagsEventOccurred { @field(String) readonly data: string;
constructor(data: string) { this.data = data; }}
class TaggingDynamicTagsService { constructor(private readonly store: IEventStore) {}
async recordProductionCritical(eventSourceId: string): Promise<void> { await this.store.eventLog.append( eventSourceId, new TaggingDynamicTagsEventOccurred('production issue'), { tags: ['production', 'critical'] }); }
async recordDevelopmentTest(eventSourceId: string): Promise<void> { await this.store.eventLog.append( eventSourceId, new TaggingDynamicTagsEventOccurred('test run'), { tags: ['development', 'testing'] }); }
async recordBatchMigration(eventSourceId: string): Promise<void> { await this.store.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.