Vertical slices
This series of tutorials builds a Library system end to end, one behavior at a time. Each tutorial corresponds to one of the four slice patterns from Event Modeling, and each one builds on the previous.
By the end you will have seen how every layer of the Cratis stack fits together: Chronicle event sourcing, Arc’s CQRS application model, and the purpose-built Components library. Each tutorial shows the backend in C#, Kotlin, Java and TypeScript. The TypeScript backend is a source preview; the backend differences below list what it and the JVM backends do differently.
Why Event Modeling?
Section titled “Why Event Modeling?”Most software projects don’t fail because of bad code. They fail because the team built the wrong thing — or built the right thing but nobody agrees on what it actually does. A product owner, a developer, and a domain expert sit in a meeting, all walk out believing they understood each other, and three weeks later reality proves otherwise.
Event Modeling addresses this directly. It is a way to design and describe information systems using a shared timeline — a visual blueprint everyone on the team can read, from developers to domain experts to product owners. Unlike a traditional specification document, an event model is collaborative and alive. It uses only three building blocks and four patterns. You can explain the core concept in minutes; the rest you learn by doing.
The three building blocks
Section titled “The three building blocks”Every Event Model is made from exactly three concepts:
| Building block | What it is | Examples |
|---|---|---|
| Events | Facts — immutable records of things that have already happened | AuthorRegistered, BookBorrowed, LoanOverdue |
| Commands | Intentions — what a user (or system) is trying to do, which will cause an event | RegisterAuthor, BorrowBook, CancelReservation |
| Read Models | Outputs — how the system informs users about what is going on | The author list, the inventory dashboard, the borrowing history |
Put them together and you have a complete picture of any workflow: a command comes in, gets validated, an event is recorded, the read model is updated — and the user sees the result.
The four patterns
Section titled “The four patterns”Every slice in an event model combines the three building blocks in one of four ways.
State Change
Section titled “State Change”A user submits a command. It gets validated. An event is recorded.
RegisterAuthor fires → AuthorRegistered is stored. The intent is explicit, the outcome is captured. This is the most common pattern — the write side of your system.
In Cratis this is: an Arc model-bound command whose handler returns a Chronicle event, optionally guarded by a command validator or a Chronicle constraint.
State View
Section titled “State View”Events are projected into a read model that the UI displays.
An Author read model gets built from AuthorRegistered events. It is always up to date, and you can rebuild it from scratch at any point by replaying the events. This is the read side — fast, purpose-built, and completely independent from the write side.
In Cratis this is: a read model built by a projection or reducer, with a query that the frontend calls — observable where the backend supports it, so the UI updates in real time.
Automation
Section titled “Automation”A processor watches a read model (think: a to-do list), picks up items, and fires a command to handle each one — entirely behind the scenes.
Sending an overdue notice when a loan passes its return date. Canceling a reservation that was never collected. Triggering a payment. No human involved; the same building blocks, automated.
In Cratis this is: a Chronicle reactor that observes the event stream and sends commands through Arc’s command pipeline back into your own system.
Translation
Section titled “Translation”When an event comes from an external system — one you don’t own — you translate its language into yours. You don’t want raw payloads as domain events. You want BookInformationReceived and MemberImported — events that mean something in your own context.
In Cratis this is: a Chronicle reactor that listens for external events and fires commands in your own system, which in turn produce domain events with your own vocabulary.
How Cratis maps to Event Modeling
Section titled “How Cratis maps to Event Modeling”| Pattern | Chronicle | Arc | Components |
|---|---|---|---|
| State Change | Event types stored in the event log, guarded by constraints | Model-bound command with a handler and validators | CommandDialog for the form UI |
| State View | Projections or reducers building a read model | Query over the read model, with generated proxies | DataPage for the listing UI |
| Automation | Reactor observing the event log | Command pipeline to fire commands | No UI — runs in the background |
| Translation | Reactor on external event streams | Command pipeline bridging to domain commands | No UI — integration layer |
Chronicle stores the facts (events), Arc carries the intent (commands) and serves the queries, and Components renders the result. For the language-specific APIs, see Arc backends.
How the backends differ
Section titled “How the backends differ”The tutorials teach one design in four backend languages. Where a backend cannot follow a step as written, the tutorial says what it does instead and links back to this table:
| Behavior | C# | Kotlin and Java | TypeScript (preview) |
|---|---|---|---|
| Author list (State View) | Live: the query observes Chronicle’s MongoDB sink | Live: observes the first page of 50 materialized instances; needs Chronicle 19.22.2 or later | Live: ChronicleReadModels.observeAll |
| Commands called without a signed-in user | The commands declare no authorization rules | Marked @AllowAnonymous, because Arc on the JVM requires an authenticated caller by default | The commands declare no authorization rules |
| How Chronicle finds a reactor’s handler (Automation) | Supported signatures and event parameter types on an IReactor | The event parameter type on a @Reactor class | The method name: the event class name with its first letter lowercased, on a @reactor() class |
| How a reactor runs commands (Automation) | It calls the injected ICommandPipeline and throws on failure | It passes each command to ChronicleCommandSideEffectHandler and throws on failure | It returns the commands; Arc’s reactorCommandResultHandler runs them |
| Constraints in command specs (State Change) | Enforced: the scenario runs an in-process Chronicle kernel | Not enforced: the scenario’s event log is in memory | Not enforced: the scenario records events in memory |
The Library system
Section titled “The Library system”All four tutorials build parts of a Library system with the following capabilities:
- Authors — register and list authors
- Members — register and list library members
- Book Catalog — register books with ISBN and associate them with authors
- Book Inventory — track how many copies are in stock
- Reservations — reserve a book for a member, subject to availability
- Lending — lend out a book and track return dates
The tutorials do not implement everything. Instead, each one picks the behavior that best illustrates a single pattern, so the focus stays on the technique, not the domain complexity.
Tutorials
Section titled “Tutorials”Work through these in order — each one builds on the context from the previous.
| Tutorial | Pattern | What you build |
|---|---|---|
| Register an author | State Change | RegisterAuthor command, AuthorRegistered event, AddAuthor React form using CommandDialog |
| List authors | State View | Author read model built from events, AllAuthors query (observable where the backend supports it), Authors listing page using DataPage |
| Cancel expired reservations | Automation | ReservationDueForExpiry to-do list, passive PendingReservation read model, and a ReservationExpiryReactor that sends CancelExpiredReservation for each overdue reservation |
| Import members from HR | Translation | MemberImportReactor that listens for HRMemberCreated external events and fires RegisterMember in the library domain |