Tagging Reactors
Tags provide a way to organize and categorize your reactors for better discoverability and management. By applying the [Tag] attribute to your reactor classes, you can assign one or more tags that describe the purpose or domain of the reactor.
[Tag] and [Tags] label the reactor itself. They do not filter which appended events the reactor 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.
Adding Tags
Section titled “Adding Tags”You can tag reactors in multiple ways:
Single Tag
Section titled “Single Tag”Apply a single tag to your reactor:
using Cratis.Chronicle;using Cratis.Chronicle.Events;using Cratis.Chronicle.Reactors;
[EventType]public record TaggingReactorsOrderPlaced(string CustomerId, string OrderId);
public interface ITaggingReactorsEmailService{ Task SendOrderConfirmation(string customerId, string orderId);}
[Tag("Notifications")]public class TaggingReactorsOrderConfirmationReactor(ITaggingReactorsEmailService emailService) : IReactor{ public Task Placed(TaggingReactorsOrderPlaced @event, EventContext context) => emailService.SendOrderConfirmation(@event.CustomerId, @event.OrderId);}import io.cratis.chronicle.events.EventContextimport io.cratis.chronicle.events.EventTypeimport io.cratis.chronicle.observation.Reactorimport io.cratis.chronicle.observation.Tag
@EventTypedata class TaggingReactorsOrderPlaced(val customerId: String, val orderId: String)
interface TaggingReactorsEmailService { suspend fun sendOrderConfirmation(customerId: String, orderId: String)}
@Tag("Notifications")@Reactorclass TaggingReactorsOrderConfirmationReactor(private val emailService: TaggingReactorsEmailService) { suspend fun placed(event: TaggingReactorsOrderPlaced, context: EventContext) = emailService.sendOrderConfirmation(event.customerId, event.orderId)}import io.cratis.chronicle.events.EventContext;import io.cratis.chronicle.events.EventType;import io.cratis.chronicle.observation.Reactor;import io.cratis.chronicle.observation.Tag;
@EventTyperecord TaggingReactorsOrderPlaced(String customerId, String orderId) {}
interface TaggingReactorsEmailService { void sendOrderConfirmation(String customerId, String orderId);}
@Tag("Notifications")@Reactorclass TaggingReactorsOrderConfirmationReactor { private final TaggingReactorsEmailService emailService;
TaggingReactorsOrderConfirmationReactor(TaggingReactorsEmailService emailService) { this.emailService = emailService; }
void placed(TaggingReactorsOrderPlaced event, EventContext context) { emailService.sendOrderConfirmation(event.customerId(), event.orderId()); }}Elixir does not support this workflow yet.import { EventContext, eventType, reactor, tag } from '@cratis/chronicle';
@eventType()class TaggingReactorsOrderPlaced { constructor(readonly customerId: string, readonly orderId: string) {}}
interface TaggingReactorsEmailService { sendOrderConfirmation(customerId: string, orderId: string): Promise<void>;}
@reactor()@tag('Notifications')class TaggingReactorsOrderConfirmationReactor { constructor(private readonly emailService: TaggingReactorsEmailService) {}
async taggingReactorsOrderPlaced(event: TaggingReactorsOrderPlaced, _context: EventContext): Promise<void> { await this.emailService.sendOrderConfirmation(event.customerId, event.orderId); }}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.Events;using Cratis.Chronicle.Reactors;
[EventType]public record TaggingReactorsCustomerRegistered(string Email, string Name);
public interface ITaggingReactorsWelcomeEmailService{ Task SendWelcomeEmail(string email, string name);}
[Tag("Notifications", "Customer", "Email")]public class TaggingReactorsCustomerNotificationReactor(ITaggingReactorsWelcomeEmailService emailService) : IReactor{ public Task Registered(TaggingReactorsCustomerRegistered @event, EventContext context) => emailService.SendWelcomeEmail(@event.Email, @event.Name);}import io.cratis.chronicle.events.EventContextimport io.cratis.chronicle.events.EventTypeimport io.cratis.chronicle.observation.Reactorimport io.cratis.chronicle.observation.Tag
@EventTypedata class TaggingReactorsCustomerRegistered(val email: String, val name: String)
interface TaggingReactorsWelcomeEmailService { suspend fun sendWelcomeEmail(email: String, name: String)}
@Tag("Notifications", "Customer", "Email")@Reactorclass TaggingReactorsCustomerNotificationReactor(private val emailService: TaggingReactorsWelcomeEmailService) { suspend fun registered(event: TaggingReactorsCustomerRegistered, context: EventContext) = emailService.sendWelcomeEmail(event.email, event.name)}import io.cratis.chronicle.events.EventContext;import io.cratis.chronicle.events.EventType;import io.cratis.chronicle.observation.Reactor;import io.cratis.chronicle.observation.Tag;
@EventTyperecord TaggingReactorsCustomerRegistered(String email, String name) {}
interface TaggingReactorsWelcomeEmailService { void sendWelcomeEmail(String email, String name);}
@Tag({"Notifications", "Customer", "Email"})@Reactorclass TaggingReactorsCustomerNotificationReactor { private final TaggingReactorsWelcomeEmailService emailService;
TaggingReactorsCustomerNotificationReactor(TaggingReactorsWelcomeEmailService emailService) { this.emailService = emailService; }
void registered(TaggingReactorsCustomerRegistered event, EventContext context) { emailService.sendWelcomeEmail(event.email(), event.name()); }}Elixir does not support this workflow yet.import { EventContext, eventType, reactor, tag } from '@cratis/chronicle';
@eventType()class TaggingReactorsCustomerRegistered { constructor(readonly email: string, readonly name: string) {}}
interface TaggingReactorsWelcomeEmailService { sendWelcomeEmail(email: string, name: string): Promise<void>;}
@reactor()@tag('Notifications', 'Customer', 'Email')class TaggingReactorsCustomerNotificationReactor { constructor(private readonly emailService: TaggingReactorsWelcomeEmailService) {}
async taggingReactorsCustomerRegistered(event: TaggingReactorsCustomerRegistered, _context: EventContext): Promise<void> { await this.emailService.sendWelcomeEmail(event.email, event.name); }}Multiple Tags (Multiple Attributes)
Section titled “Multiple Tags (Multiple Attributes)”Apply multiple [Tag] attributes:
using Cratis.Chronicle;using Cratis.Chronicle.Events;using Cratis.Chronicle.Reactors;
[EventType]public record TaggingReactorsProductStockChanged(string ProductId, int NewQuantity);
public interface ITaggingReactorsInventoryApi{ Task UpdateStock(string productId, int newQuantity);}
[Tag("Integration")][Tag("ExternalAPI")][Tag("Inventory")]public class TaggingReactorsInventorySyncReactor(ITaggingReactorsInventoryApi inventoryApi) : IReactor{ public Task StockChanged(TaggingReactorsProductStockChanged @event, EventContext context) => inventoryApi.UpdateStock(@event.ProductId, @event.NewQuantity);}import io.cratis.chronicle.events.EventContextimport io.cratis.chronicle.events.EventTypeimport io.cratis.chronicle.observation.Reactorimport io.cratis.chronicle.observation.Tag
@EventTypedata class TaggingReactorsProductStockChanged(val productId: String, val newQuantity: Int)
interface TaggingReactorsInventoryApi { suspend fun updateStock(productId: String, newQuantity: Int)}
@Tag("Integration")@Tag("ExternalAPI")@Tag("Inventory")@Reactorclass TaggingReactorsInventorySyncReactor(private val inventoryApi: TaggingReactorsInventoryApi) { suspend fun stockChanged(event: TaggingReactorsProductStockChanged, context: EventContext) = inventoryApi.updateStock(event.productId, event.newQuantity)}import io.cratis.chronicle.events.EventContext;import io.cratis.chronicle.events.EventType;import io.cratis.chronicle.observation.Reactor;import io.cratis.chronicle.observation.Tag;
@EventTyperecord TaggingReactorsProductStockChanged(String productId, int newQuantity) {}
interface TaggingReactorsInventoryApi { void updateStock(String productId, int newQuantity);}
@Tag("Integration")@Tag("ExternalAPI")@Tag("Inventory")@Reactorclass TaggingReactorsInventorySyncReactor { private final TaggingReactorsInventoryApi inventoryApi;
TaggingReactorsInventorySyncReactor(TaggingReactorsInventoryApi inventoryApi) { this.inventoryApi = inventoryApi; }
void stockChanged(TaggingReactorsProductStockChanged event, EventContext context) { inventoryApi.updateStock(event.productId(), event.newQuantity()); }}Elixir does not support this workflow yet.import { EventContext, eventType, reactor, tag } from '@cratis/chronicle';
@eventType()class TaggingReactorsProductStockChanged { constructor(readonly productId: string, readonly newQuantity: number) {}}
interface TaggingReactorsInventoryApi { updateStock(productId: string, newQuantity: number): Promise<void>;}
@reactor()@tag('Integration')@tag('ExternalAPI')@tag('Inventory')class TaggingReactorsInventorySyncReactor { constructor(private readonly inventoryApi: TaggingReactorsInventoryApi) {}
async taggingReactorsProductStockChanged(event: TaggingReactorsProductStockChanged, _context: EventContext): Promise<void> { await this.inventoryApi.updateStock(event.productId, event.newQuantity); }}Mixed Approach
Section titled “Mixed Approach”Combine both approaches:
using Cratis.Chronicle;using Cratis.Chronicle.Events;using Cratis.Chronicle.Reactors;
[EventType]public record TaggingReactorsOrderShipped(string PhoneNumber, string TrackingNumber);
public interface ITaggingReactorsSmsService{ Task SendShippingNotification(string phoneNumber, string trackingNumber);}
[Tag("Notifications", "SMS")][Tag("Customer")]public class TaggingReactorsSmsNotificationReactor(ITaggingReactorsSmsService smsService) : IReactor{ public Task Shipped(TaggingReactorsOrderShipped @event, EventContext context) => smsService.SendShippingNotification(@event.PhoneNumber, @event.TrackingNumber);}import io.cratis.chronicle.events.EventContextimport io.cratis.chronicle.events.EventTypeimport io.cratis.chronicle.observation.Reactorimport io.cratis.chronicle.observation.Tag
@EventTypedata class TaggingReactorsOrderShipped(val phoneNumber: String, val trackingNumber: String)
interface TaggingReactorsSmsService { suspend fun sendShippingNotification(phoneNumber: String, trackingNumber: String)}
@Tag("Notifications", "SMS")@Tag("Customer")@Reactorclass TaggingReactorsSmsNotificationReactor(private val smsService: TaggingReactorsSmsService) { suspend fun shipped(event: TaggingReactorsOrderShipped, context: EventContext) = smsService.sendShippingNotification(event.phoneNumber, event.trackingNumber)}import io.cratis.chronicle.events.EventContext;import io.cratis.chronicle.events.EventType;import io.cratis.chronicle.observation.Reactor;import io.cratis.chronicle.observation.Tag;
@EventTyperecord TaggingReactorsOrderShipped(String phoneNumber, String trackingNumber) {}
interface TaggingReactorsSmsService { void sendShippingNotification(String phoneNumber, String trackingNumber);}
@Tag({"Notifications", "SMS"})@Tag("Customer")@Reactorclass TaggingReactorsSmsNotificationReactor { private final TaggingReactorsSmsService smsService;
TaggingReactorsSmsNotificationReactor(TaggingReactorsSmsService smsService) { this.smsService = smsService; }
void shipped(TaggingReactorsOrderShipped event, EventContext context) { smsService.sendShippingNotification(event.phoneNumber(), event.trackingNumber()); }}Elixir does not support this workflow yet.import { EventContext, eventType, reactor, tag } from '@cratis/chronicle';
@eventType()class TaggingReactorsOrderShipped { constructor(readonly phoneNumber: string, readonly trackingNumber: string) {}}
interface TaggingReactorsSmsService { sendShippingNotification(phoneNumber: string, trackingNumber: string): Promise<void>;}
@reactor()@tag('Notifications', 'SMS')@tag('Customer')class TaggingReactorsSmsNotificationReactor { constructor(private readonly smsService: TaggingReactorsSmsService) {}
async taggingReactorsOrderShipped(event: TaggingReactorsOrderShipped, _context: EventContext): Promise<void> { await this.smsService.sendShippingNotification(event.phoneNumber, event.trackingNumber); }}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 reactors: Use tags to group reactors that serve similar purposes
Common Tag Examples
Section titled “Common Tag Examples”Here are some common patterns for tagging reactors:
using Cratis.Chronicle;using Cratis.Chronicle.Reactors;
// By integration type[Tag("Notifications", "ExternalAPI", "MessageQueue", "FileSystem")]// By domain[Tag("Sales", "Inventory", "Customer", "Shipping")]// By communication channel[Tag("Email", "SMS", "Push", "Webhook")]// By purpose[Tag("Integration", "Alerting", "Monitoring", "Automation")]// By stakeholder[Tag("Customer", "Operations", "Finance", "Support")]public class TaggingReactorsCategoryExamplesReactor : IReactor;import io.cratis.chronicle.observation.Reactorimport io.cratis.chronicle.observation.Tag
// By integration type@Tag("Notifications", "ExternalAPI", "MessageQueue", "FileSystem")// By domain@Tag("Sales", "Inventory", "Customer", "Shipping")// By communication channel@Tag("Email", "SMS", "Push", "Webhook")// By purpose@Tag("Integration", "Alerting", "Monitoring", "Automation")// By stakeholder@Tag("Customer", "Operations", "Finance", "Support")@Reactorclass TaggingReactorsCategoryExamplesReactorimport io.cratis.chronicle.observation.Reactor;import io.cratis.chronicle.observation.Tag;
// By integration type@Tag({"Notifications", "ExternalAPI", "MessageQueue", "FileSystem"})// By domain@Tag({"Sales", "Inventory", "Customer", "Shipping"})// By communication channel@Tag({"Email", "SMS", "Push", "Webhook"})// By purpose@Tag({"Integration", "Alerting", "Monitoring", "Automation"})// By stakeholder@Tag({"Customer", "Operations", "Finance", "Support"})@Reactorclass TaggingReactorsCategoryExamplesReactor {}Elixir does not support this workflow yet.import { reactor, tag } from '@cratis/chronicle';
@reactor()// By integration type@tag('Notifications', 'ExternalAPI', 'MessageQueue', 'FileSystem')// By domain@tag('Sales', 'Inventory', 'Customer', 'Shipping')// By communication channel@tag('Email', 'SMS', 'Push', 'Webhook')// By purpose@tag('Integration', 'Alerting', 'Monitoring', 'Automation')// By stakeholder@tag('Customer', 'Operations', 'Finance', 'Support')class TaggingReactorsCategoryExamplesReactor {}Querying by Tag
Section titled “Querying by Tag”Tags stored in the event store definition can be used for:
- Filtering and searching for specific reactors
- Organizing reactors in administrative interfaces
- Generating documentation
- Managing reactor deployments by tag
- Monitoring and alerting based on tag groups
- Controlling reactor activation 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
- Reducers Tagging - Tagging reducers