Skip to content

Define an event source

Use this page to declare an event source definition. To write events through it, read Append through a 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;

View C# snippet source on GitHub

AttributePropertyMeaning
[EventSource]name (constructor)The event source type written on every event appended through the definition
DescriptionShown in the Workbench
ConcurrencyThe default concurrency dimensions for every stream
[EventStream]name (constructor)The event stream type written on events appended to the stream
DescriptionShown in the Workbench
ConcurrencyThe 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.

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;

View C# snippet source on GitHub

  • An explicit name in [EventSource("Loan")] wins.
  • Without one, the name is the type name with a trailing EventSource removed. A type that is itself called EventSource keeps its name.
  • The attribute is optional. Without it the definition has no description and declares no concurrency dimensions.
  • Names are compared exactly. Account and account are different event source types.

Two definitions with the same name make discovery throw DuplicateEventSourceName. A definition that declares the same stream twice throws DuplicateEventStreamName.

ConcurrencyDimensions is a flags enum. It says which values of an append take part in its concurrency check.

FlagThe guard covers events with the same
EventSourceIdevent source id
EventSourceTypeevent source type
EventStreamTypeevent stream type
EventStreamIdevent stream id
NoneNothing 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.

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

View C# snippet source on GitHub

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.

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.

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

View C# snippet source on GitHub

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.