Skip to content

1. Your first event

Every library starts the same way: a book shows up. So that’s where we’ll start too — by recording that fact. By the end of this chapter you’ll have written your first event to Chronicle and understood why we reach for an event instead of a database row.

Your instinct, coming from most databases, is probably to create a Book row and INSERT it. Hold that thought — because it throws away the most interesting thing: that the book arrived, and when. In event sourcing we record what happened. The book arriving is a fact, and facts have a few properties: they’re immutable (it happened; you can’t un-happen it), and they’re named in the past tense.

So let’s name it. A BookAdded event, as a record marked with [EventType]:

using Cratis.Chronicle.Events;
[EventType]
public record BookAdded(string Title, string Isbn);

A couple of things worth noticing here:

  • [EventType] carries no name — Chronicle uses the type’s name (BookAdded) as the event’s identity. That’s a small thing now, but it’s why you’ll never hand-maintain a string registry of event names.
  • The properties aren’t nullable. An event states what was true the moment it happened. If you ever find yourself reaching for a nullable property, that’s Chronicle nudging you: you probably have a second fact hiding in there, and it deserves its own event.

Every event is about something — here, a specific book. Chronicle calls that the event source, and the events that share a source form that book’s own little stream of history. In C#, we’ll identify a book with a strongly-typed id rather than a bare Guid, so the compiler stops us from ever mixing a book’s id up with, say, a member’s:

using Cratis.Chronicle.Events;
public record BookId(Guid Value) : EventSourceId<Guid>(Value)
{
public static BookId New() => new(Guid.NewGuid());
}

By inheriting from EventSourceId<Guid>, BookId becomes a specialized concept — a ConceptAs<> targeting EventSourceId — so it converts to and from the stream key automatically: you hand a BookId straight to Chronicle, no ceremony. And because the type itself says “I am an event source id”, conventions elsewhere in the stack pick it up — Arc, for instance, recognizes an EventSourceId-derived parameter on a command and resolves which stream to write to without any wiring from you. One small record, and the whole stack knows what a book’s identity is. The event source concept goes deeper on how these per-thing streams of facts work.

Kotlin, Java, Elixir, and TypeScript don’t currently have an equivalent typed-id wrapper — event source ids in those clients are plain strings, so the compiler can’t stop you from mixing up a book’s id and a member’s the way it can in C#.

Now the moment itself. With a ChronicleClient connected to your event store, append the event against the book’s id (in C#, the strongly-typed BookId; in the other clients, a plain string you generate yourself):

using Cratis.Chronicle;
public static class TutorialFirstEventAppend
{
public static async Task<BookId> AddBook(IEventStore eventStore)
{
var book = BookId.New();
await eventStore.EventLog.Append(book, new BookAdded("The Pragmatic Programmer", "978-0135957059"));
return book;
}
}

Run it. Nothing dramatic appears on screen — and that’s exactly right. Behind that one line, Chronicle validated the event against its registered schema, assigned it the next sequence number, and committed it to the event log — permanently, and in order. The fact is now part of your system’s history; nothing will ever quietly overwrite it.

  • Modeled a real-world moment as an immutable event (BookAdded) instead of a row to be updated later.
  • Gave the book a strongly-typed event source id so its events form one stream.
  • Appended that event to the log — your first permanent fact.

A fact you can’t query, though, isn’t much use yet — right now the book exists only as history. In the next chapter we’ll fix that: we’ll teach Chronicle to fold this stream of events into a Books read model you can actually query, and watch it update itself as more events arrive. Onward →