Define an event source
Use this page to declare an event source definition. To write events through it, read Append through a definition.
Declare the definition
Section titled “Declare the definition”A definition is a class that implements IEventSource. Put [EventSource] on it, add one [EventStream] for each stream, and leave the body empty.
using Cratis.Chronicle.EventSources;
[EventSource("Account", Description = "A customer's bank account", Concurrency = ConcurrencyDimensions.EventSourceId)][EventStream("Onboarding", Description = "Opening the account and verifying the customer")][EventStream( "Transactions", Description = "Deposits and withdrawals, one stream per month", Concurrency = ConcurrencyDimensions.EventSourceId | ConcurrencyDimensions.EventStreamType | ConcurrencyDimensions.EventStreamId)]public class AccountEventSource : IEventSource;| Attribute | Property | Meaning |
|---|---|---|
[EventSource] | name (constructor) | The event source type written on every event appended through the definition |
Description | Shown in the Workbench | |
Concurrency | The default concurrency dimensions for every stream | |
[EventStream] | name (constructor) | The event stream type written on events appended to the stream |
Description | Shown in the Workbench | |
Concurrency | The dimensions for this stream. When none, the event source’s dimensions apply |
Chronicle never creates an instance of the class. It reads the attributes and registers the result.
Choose the names
Section titled “Choose the names”The two names are what end up on stored events and what observers match on, so choose them as you would an event type name and keep them stable.
using Cratis.Chronicle.EventSources;
// The event source type is "Loan": an explicit name wins.[EventSource("Loan")]public class BankLoanDefinition : IEventSource;
// The event source type is "ShoppingCart": the "EventSource" suffix is removed from the type name.[EventSource(Description = "A customer's shopping cart")]public class ShoppingCartEventSource : IEventSource;
// The attribute is optional. The event source type is "Invoice", with no description and no concurrency dimensions.public class InvoiceEventSource : IEventSource;- An explicit name in
[EventSource("Loan")]wins. - Without one, the name is the type name with a trailing
EventSourceremoved. A type that is itself calledEventSourcekeeps its name. - The attribute is optional. Without it the definition has no description and declares no concurrency dimensions.
- Names are compared exactly.
Accountandaccountare different event source types.
Two definitions with the same name make discovery throw DuplicateEventSourceName. A definition that declares the same stream twice throws DuplicateEventStreamName.
Choose the concurrency dimensions
Section titled “Choose the concurrency dimensions”ConcurrencyDimensions is a flags enum. It says which values of an append take part in its concurrency check.
| Flag | The guard covers events with the same |
|---|---|
EventSourceId | event source id |
EventSourceType | event source type |
EventStreamType | event stream type |
EventStreamId | event stream id |
None | Nothing is declared. The append keeps the guard it would have had without a definition |
A stream that declares dimensions uses them. A stream that declares none inherits the event source’s. Concurrency shows what each combination conflicts with.
Choose the stream ids
Section titled “Choose the stream ids”A stream declares a type only. The stream id is a value you pass on each append, and Chronicle does not impose a format or a template for it. Keep the convention in one place so every writer produces the same string:
using Cratis.Chronicle.Events;
// Chronicle does not impose a format for an event stream id. Keep the convention in one place.public static class TransactionStreams{ public static EventStreamId ForMonth(DateOnly date) => new($"{date.Year:0000}-{date.Month:00}");}Streams that are not instances of anything, such as an Onboarding stream that exists once per account, can use the default stream id by leaving it out.
Where Chronicle finds definitions
Section titled “Where Chronicle finds definitions”The client discovers every non-abstract, non-generic class that implements IEventSource in the application’s assemblies, together with its other artifacts. With explicit artifact registration you nominate definitions in the composition root instead. Registration with the kernel happens when the event store connects. How it works has the sequence.
Check what is registered
Section titled “Check what is registered”Read the definition back from the client:
using Cratis.Chronicle;using Cratis.Chronicle.EventSources;
public static class DefinitionInspector{ public static IEnumerable<string> DescribeStreams(IEventStore eventStore) { var definition = eventStore.EventSources.GetFor(typeof(AccountEventSource));
// ConcurrencyFor returns the stream's dimensions when it declares any, otherwise the event source's. return definition.Streams.Select(stream => $"{definition.Name}/{stream.Name}: {definition.ConcurrencyFor(stream)}"); }}IEventStore.EventSources is available after the event store has discovered its artifacts. The Workbench lists the registered definitions of an event store on its Event Sources page: name, description, owner, default concurrency and streams, with a detail pane for each stream’s own dimensions. See Workbench.
Next steps
Section titled “Next steps”- Append through a definition
- Concurrency
- Migrate from string routing
- Dynamic Consistency Boundary in Chronicle: where a definition fits a decision model and an append condition.