<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:fh="http://purl.org/syndication/history/1.0"><channel><title>Cratis Blog | Blog</title><description>The Cratis blog — essays and engineering explainers on event sourcing, CQRS, and building the open-source (MIT) Cratis stack: Chronicle, Arc, Components, and friends.</description><link>https://blog.cratis.io/</link><language>en</language><fh:complete/><atom:link rel="self" href="https://blog.cratis.io/rss.xml"/><item><title>Event sourcing in .NET with Chronicle: from zero to first projection</title><link>https://blog.cratis.io/event-sourcing-in-dotnet-with-chronicle/</link><guid isPermaLink="true">https://blog.cratis.io/event-sourcing-in-dotnet-with-chronicle/</guid><description>Run Chronicle locally in one container, append your first events from a plain .NET console app, and watch them become read models — every command in this post was executed against the exact versions it names.</description><pubDate>Fri, 28 Aug 2026 18:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Event sourcing has a reputation for heavy setup: a store, a bus, projections infrastructure, and a day of wiring before the first event lands. This post takes the shortest honest path instead: one Docker container, one console project, and about ninety lines of C# that append events, project them into read models, and react to them — with the full event history inspectable in a browser at the end.&lt;/p&gt;
&lt;p&gt;Everything below was executed as written. The versions are pinned so you can reproduce the run exactly:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Piece&lt;/th&gt;
&lt;th&gt;Version&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Chronicle kernel container&lt;/td&gt;
&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;cratis/chronicle:latest-development&lt;/code&gt;, digest &lt;code dir=&quot;auto&quot;&gt;sha256:a423610f88e088e53d3bb821fa972a35dc84e254297bf8d720653316b68e61bf&lt;/code&gt; (Chronicle Server 16.38.5)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Client package&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://www.nuget.org/packages/Cratis.Chronicle&quot;&gt;&lt;code dir=&quot;auto&quot;&gt;Cratis.Chronicle&lt;/code&gt;&lt;/a&gt; 16.38.5&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;.NET SDK&lt;/td&gt;
&lt;td&gt;8.0.408 (&lt;code dir=&quot;auto&quot;&gt;net8.0&lt;/code&gt; target)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Extra package on .NET 8&lt;/td&gt;
&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;System.Collections.Immutable&lt;/code&gt; 10.0.0 (see the note in step 2)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;code dir=&quot;auto&quot;&gt;latest-development&lt;/code&gt; is a moving tag; if you pull it later you may get a newer kernel. The digest above is the exact image this post was verified against. &lt;a href=&quot;https://cratis.io/chronicle/&quot;&gt;Chronicle&lt;/a&gt; and its bundled local Workbench are MIT-licensed, self-hosted software — what you run here is yours to run.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;what-you-will-build&quot;&gt;What you will build&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;A minimal .NET console application for a tiny library domain: a book arrives, gets borrowed, and comes back. Each of those facts is an event appended to Chronicle’s event log. Two read models are projected from those events — declaratively, with no update code — and a reactor performs a side effect when a book is returned. At the end you open the bundled Workbench and see the whole history.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;1-run-chronicle&quot;&gt;1. Run Chronicle&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;The development image bundles the Chronicle kernel and its MongoDB storage in a single container — no separate database setup. Start it bound to loopback only, so nothing outside your machine can reach it:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;docker&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;run&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-d&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--name&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;chronicle&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;\&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;-p&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;127.0.0.1:35000:35000&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;\&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;cratis/chronicle:latest-development&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Port &lt;code dir=&quot;auto&quot;&gt;35000&lt;/code&gt; carries gRPC, the REST API, and the Workbench on a single TLS port, using a self-signed development certificate the container generates at startup. Give it a few seconds, then confirm it is up:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;docker&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;logs&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;chronicle&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;2&gt;&amp;#x26;1&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;|&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;grep&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;Starting Cratis Chronicle Server&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;Starting Cratis Chronicle Server - Version 16.38.5.0&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;div&gt;&lt;h2 id=&quot;2-create-the-application&quot;&gt;2. Create the application&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Create a console project and add the Chronicle client at the pinned version:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;mkdir&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Quickstart&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&amp;#x26;&amp;#x26;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;cd&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Quickstart&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;dotnet&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;new&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;console&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--framework&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;net8.0&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;dotnet&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;add&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;package&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Cratis.Chronicle&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--version&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;16.38.5&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;dotnet&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;add&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;package&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;System.Collections.Immutable&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--version&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;10.0.0&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Why the extra package?&lt;/strong&gt; On a &lt;code dir=&quot;auto&quot;&gt;net8.0&lt;/code&gt; target, &lt;code dir=&quot;auto&quot;&gt;Cratis.Chronicle&lt;/code&gt; 16.38.5 fails at startup with &lt;code dir=&quot;auto&quot;&gt;Could not load file or assembly &apos;System.Collections.Immutable, Version=10.0.0.0&apos;&lt;/code&gt; unless you add the package explicitly — the client assembly references it, but the package does not declare the dependency. On the .NET 10 SDK with a &lt;code dir=&quot;auto&quot;&gt;net10.0&lt;/code&gt; target and &lt;code dir=&quot;auto&quot;&gt;Cratis.Chronicle&lt;/code&gt; 17.0.0, the extra package is not needed; we verified both combinations against the same kernel.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;div&gt;&lt;h2 id=&quot;3-define-the-events&quot;&gt;3. Define the events&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Events are immutable facts, modeled as records marked with &lt;code dir=&quot;auto&quot;&gt;[EventType]&lt;/code&gt;. The attribute is how Chronicle discovers the type — the type name is the identity, so there is nothing else to configure:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;using&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Cratis&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Chronicle&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Events&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;EventType&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;public&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;record&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;BookAdded&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt;&lt;span&gt; Title&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt;&lt;span&gt; Isbn&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;EventType&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;public&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;record&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;BookBorrowed&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt;&lt;span&gt; MemberName&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;EventType&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;public&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;record&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;BookReturned&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;&lt;code dir=&quot;auto&quot;&gt;BookReturned&lt;/code&gt; carries no data at all. That it happened, on a particular book’s stream, is the whole story — not every fact needs a payload.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;4-declare-the-read-models&quot;&gt;4. Declare the read models&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Events are the write side. To read current state, you declare the shape you want and which events feed each field, and Chronicle keeps it in sync — you never write an &lt;code dir=&quot;auto&quot;&gt;UPDATE&lt;/code&gt;:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;using&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Cratis&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Chronicle&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Keys&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;using&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Cratis&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Chronicle&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Projections&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;ModelBound&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;FromEvent&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;BookAdded&lt;/span&gt;&lt;span&gt;&gt;]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;public&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;record&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Book&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;Key&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;Guid&lt;/span&gt;&lt;span&gt;&lt;span&gt; Id&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt;&lt;span&gt; Title&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt;&lt;span&gt; Isbn&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;SetValue&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;BookAdded&lt;/span&gt;&lt;span&gt;&gt;(&lt;/span&gt;&lt;span&gt;false&lt;/span&gt;&lt;span&gt;)]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;SetValue&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;BookBorrowed&lt;/span&gt;&lt;span&gt;&gt;(&lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt;)]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;SetValue&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;BookReturned&lt;/span&gt;&lt;span&gt;&gt;(&lt;/span&gt;&lt;span&gt;false&lt;/span&gt;&lt;span&gt;)]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;bool&lt;/span&gt;&lt;span&gt;&lt;span&gt; OnLoan&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;SetFrom&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;BookBorrowed&lt;/span&gt;&lt;span&gt;&gt;(&lt;/span&gt;&lt;span&gt;nameof&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;BookBorrowed&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;MemberName&lt;/span&gt;&lt;span&gt;))]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt;&lt;span&gt;?&lt;/span&gt;&lt;span&gt; BorrowedBy&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Read the attributes as a sentence: a book enters the view from &lt;code dir=&quot;auto&quot;&gt;BookAdded&lt;/code&gt;; &lt;code dir=&quot;auto&quot;&gt;OnLoan&lt;/code&gt; flips with each borrow and return; &lt;code dir=&quot;auto&quot;&gt;BorrowedBy&lt;/code&gt; is whoever borrowed it. &lt;code dir=&quot;auto&quot;&gt;Title&lt;/code&gt; and &lt;code dir=&quot;auto&quot;&gt;Isbn&lt;/code&gt; map from the event by naming convention — no per-property attributes needed when the names match.&lt;/p&gt;
&lt;p&gt;The second read model answers “what is out on loan right now?” by existing only while a loan is active:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;FromEvent&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;BookBorrowed&lt;/span&gt;&lt;span&gt;&gt;]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;RemovedWith&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;BookReturned&lt;/span&gt;&lt;span&gt;&gt;]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;public&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;record&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;BorrowedBook&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;Key&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;Guid&lt;/span&gt;&lt;span&gt;&lt;span&gt; Id&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt;&lt;span&gt; MemberName&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;When a &lt;code dir=&quot;auto&quot;&gt;BookBorrowed&lt;/code&gt; lands, a &lt;code dir=&quot;auto&quot;&gt;BorrowedBook&lt;/code&gt; appears; when the matching &lt;code dir=&quot;auto&quot;&gt;BookReturned&lt;/code&gt; arrives, it is removed. No flag to maintain, no filter to remember.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;5-react-to-an-event&quot;&gt;5. React to an event&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;When you need to do something the moment a fact lands — notify someone, call another system — you write a reactor. &lt;code dir=&quot;auto&quot;&gt;IReactor&lt;/code&gt; is a marker interface; add a method whose first parameter is the event you care about, and Chronicle routes matching events to it:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;using&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Cratis&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Chronicle&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Events&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;using&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Cratis&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Chronicle&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Reactors&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;public&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;BookReturnedNotifier&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;IReactor&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;public&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Task&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Returned&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;BookReturned&lt;/span&gt;&lt;span&gt;&lt;span&gt; @event&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;EventContext&lt;/span&gt;&lt;span&gt;&lt;span&gt; context&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;Console&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;WriteLine&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&lt;span&gt;$&quot;&lt;/span&gt;&lt;span&gt;Reactor: book &lt;/span&gt;&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;context&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;EventSourceId&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;&lt;span&gt; was returned — notify the next member in line.&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Task&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;CompletedTask&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;div&gt;&lt;h2 id=&quot;6-connect-append-and-query&quot;&gt;6. Connect, append, and query&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Now the program that ties it together. In a console app there is no host or DI container, so you create the &lt;code dir=&quot;auto&quot;&gt;ChronicleClient&lt;/code&gt; yourself, open an event store, and explicitly ask Chronicle to discover and register the artifacts you just defined:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;using&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Cratis&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Chronicle&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;using&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Cratis&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Chronicle&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Connections&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;using&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;var&lt;/span&gt;&lt;span&gt; client &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;new&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;ChronicleClient&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;ChronicleConnectionString&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Development&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;var&lt;/span&gt;&lt;span&gt; eventStore &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;await&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;client&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;GetEventStore&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;Quickstart&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;Console&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;WriteLine&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&lt;span&gt;$&quot;&lt;/span&gt;&lt;span&gt;Connected to event store: &lt;/span&gt;&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;eventStore&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Name&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;await&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;eventStore&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;DiscoverAll&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;await&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;eventStore&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;RegisterAll&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;var&lt;/span&gt;&lt;span&gt; bookId &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Guid&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;NewGuid&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;var&lt;/span&gt;&lt;span&gt; appendResult &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;await&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;eventStore&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;EventLog&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Append&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;bookId&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;new&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;BookAdded&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;The Pragmatic Programmer&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;978-0135957059&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;));&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;Console&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;WriteLine&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&lt;span&gt;$&quot;&lt;/span&gt;&lt;span&gt;Appended BookAdded at sequence &lt;/span&gt;&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;appendResult&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;SequenceNumber&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt; (success: &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;appendResult&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;IsSuccess&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;appendResult&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;await&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;eventStore&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;EventLog&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Append&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;bookId&lt;/span&gt;&lt;span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;new&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;BookBorrowed&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;Jane Doe&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;));&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;Console&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;WriteLine&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&lt;span&gt;$&quot;&lt;/span&gt;&lt;span&gt;Appended BookBorrowed at sequence &lt;/span&gt;&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;appendResult&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;SequenceNumber&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt; (success: &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;appendResult&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;IsSuccess&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;// Give the freshly registered read models a moment to come online before the first query.&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;await&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Task&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Delay&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;TimeSpan&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;FromSeconds&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;5&lt;/span&gt;&lt;span&gt;));&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;var&lt;/span&gt;&lt;span&gt; books &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;await&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;eventStore&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;ReadModels&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;GetInstances&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;Book&lt;/span&gt;&lt;span&gt;&gt;();&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;foreach&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;&lt;span&gt;var&lt;/span&gt;&lt;span&gt; book &lt;/span&gt;&lt;span&gt;in&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;books&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;Console&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;WriteLine&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&lt;span&gt;$&quot;&lt;/span&gt;&lt;span&gt;Book read model: &lt;/span&gt;&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;book&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Title&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;book&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Isbn&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;) OnLoan=&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;book&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;OnLoan&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt; BorrowedBy=&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;book&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;BorrowedBy&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;var&lt;/span&gt;&lt;span&gt; borrowed &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;await&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;eventStore&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;ReadModels&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;GetInstances&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;BorrowedBook&lt;/span&gt;&lt;span&gt;&gt;();&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;foreach&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;&lt;span&gt;var&lt;/span&gt;&lt;span&gt; loan &lt;/span&gt;&lt;span&gt;in&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;borrowed&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;Console&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;WriteLine&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&lt;span&gt;$&quot;&lt;/span&gt;&lt;span&gt;BorrowedBook read model: &lt;/span&gt;&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;loan&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Id&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt; borrowed by &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;loan&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;MemberName&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;appendResult&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;await&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;eventStore&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;EventLog&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Append&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;bookId&lt;/span&gt;&lt;span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;new&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;BookReturned&lt;/span&gt;&lt;span&gt;());&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;Console&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;WriteLine&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&lt;span&gt;$&quot;&lt;/span&gt;&lt;span&gt;Appended BookReturned at sequence &lt;/span&gt;&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;appendResult&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;SequenceNumber&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt; (success: &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;appendResult&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;IsSuccess&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;// Give the reactor a moment to observe the event.&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;await&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Task&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Delay&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;TimeSpan&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;FromSeconds&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;5&lt;/span&gt;&lt;span&gt;));&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;books&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;await&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;eventStore&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;ReadModels&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;GetInstances&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;Book&lt;/span&gt;&lt;span&gt;&gt;();&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;foreach&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;&lt;span&gt;var&lt;/span&gt;&lt;span&gt; book &lt;/span&gt;&lt;span&gt;in&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;books&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;Console&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;WriteLine&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&lt;span&gt;$&quot;&lt;/span&gt;&lt;span&gt;Book read model after return: &lt;/span&gt;&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;book&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Title&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt; OnLoan=&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;book&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;OnLoan&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;borrowed&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;await&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;eventStore&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;ReadModels&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;GetInstances&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;BorrowedBook&lt;/span&gt;&lt;span&gt;&gt;();&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;Console&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;WriteLine&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&lt;span&gt;$&quot;&lt;/span&gt;&lt;span&gt;BorrowedBook read models after return: &lt;/span&gt;&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;borrowed&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Count&lt;/span&gt;&lt;span&gt;()}&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;&lt;code dir=&quot;auto&quot;&gt;ChronicleConnectionString.Development&lt;/code&gt; points at the local development kernel on &lt;code dir=&quot;auto&quot;&gt;chronicle://localhost:35000&lt;/code&gt; with the built-in development credentials — the same connection &lt;code dir=&quot;auto&quot;&gt;new ChronicleClient()&lt;/code&gt; uses with no arguments. The event source id (&lt;code dir=&quot;auto&quot;&gt;bookId&lt;/code&gt;) is the identity of the thing each fact is about; every event appended against it becomes part of that book’s stream of history.&lt;/p&gt;
&lt;p&gt;The two &lt;code dir=&quot;auto&quot;&gt;Task.Delay&lt;/code&gt; calls deserve honesty: &lt;code dir=&quot;auto&quot;&gt;GetInstances&lt;/code&gt; replays events on demand, but registration of freshly declared read models and delivery to reactors are asynchronous. On our machine, querying immediately after the very first registration returned empty results; five seconds was comfortably enough. In a long-running application this is a non-issue — registration happens once at startup.&lt;/p&gt;
&lt;p&gt;Put the event, read model, and reactor definitions after the top-level statements (or in separate files) and run it:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;dotnet&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;run&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;Connected to event store: Quickstart&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;Appended BookAdded at sequence 0 (success: True)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;Appended BookBorrowed at sequence 1 (success: True)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;Book read model: The Pragmatic Programmer (978-0135957059) OnLoan=True BorrowedBy=Jane Doe&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;BorrowedBook read model: c6dc1472-3d85-4dd9-97bf-60e009e29caf borrowed by Jane Doe&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;Appended BookReturned at sequence 2 (success: True)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;Reactor: book c6dc1472-3d85-4dd9-97bf-60e009e29caf was returned — notify the next member in line.&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;Book read model after return: The Pragmatic Programmer OnLoan=False&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;BorrowedBook read models after return: 0&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;That is the whole loop — append, project, react. The book’s &lt;code dir=&quot;auto&quot;&gt;OnLoan&lt;/code&gt; flag flipped, the &lt;code dir=&quot;auto&quot;&gt;BorrowedBook&lt;/code&gt; appeared and disappeared, and the reactor fired — and you never wrote an update statement.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;7-see-the-history&quot;&gt;7. See the history&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;State-based storage shows you what the data is. Chronicle also shows you every fact that made it so. Open the bundled Workbench at &lt;a href=&quot;https://localhost:35000&quot;&gt;https://localhost:35000&lt;/a&gt; — your browser will warn about the self-signed development certificate; that is expected for the local development image. Log in with the development image’s default credentials (username &lt;code dir=&quot;auto&quot;&gt;Admin&lt;/code&gt;, password &lt;code dir=&quot;auto&quot;&gt;ChangeMeNow!&lt;/code&gt; — see &lt;a href=&quot;https://cratis.io/chronicle/workbench/development/&quot;&gt;Workbench development mode&lt;/a&gt;), pick the &lt;code dir=&quot;auto&quot;&gt;Quickstart&lt;/code&gt; event store, and select &lt;strong&gt;Sequences&lt;/strong&gt;: your &lt;code dir=&quot;auto&quot;&gt;BookAdded&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;BookBorrowed&lt;/code&gt;, and &lt;code dir=&quot;auto&quot;&gt;BookReturned&lt;/code&gt; are sitting there in order, permanent, with their event source id and timestamps. The Workbench is a bundled local browser surface for authorized inspection of Chronicle runtime state — run the program again and watch new events arrive.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;clean-up&quot;&gt;Clean up&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;When you are done, remove the container and the image:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;docker&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;rm&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-f&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;chronicle&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;docker&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;rmi&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;cratis/chronicle:latest-development&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Deleting your &lt;code dir=&quot;auto&quot;&gt;Quickstart&lt;/code&gt; folder removes everything else — event data lives inside the container, so removing it removes the data too.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;where-to-go-next&quot;&gt;Where to go next&lt;/h2&gt;&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;The &lt;a href=&quot;https://cratis.io/chronicle/get-started/console/&quot;&gt;console quickstart&lt;/a&gt; covers this same path in the documentation, including querying the materialized read models in MongoDB directly.&lt;/li&gt;
&lt;li&gt;The &lt;a href=&quot;https://cratis.io/chronicle/tutorial/&quot;&gt;tutorial&lt;/a&gt; builds the library domain one concept at a time — strongly-typed ids, hosts, and DI included.&lt;/li&gt;
&lt;li&gt;The &lt;a href=&quot;https://cratis.io/chronicle/get-started/choose-hosting-model/&quot;&gt;ASP.NET Core and Worker Service guides&lt;/a&gt; show the same pieces with the host’s DI container doing the wiring.&lt;/li&gt;
&lt;li&gt;Chronicle also ships &lt;a href=&quot;https://cratis.io/chronicle/clients/&quot;&gt;TypeScript, Kotlin/Java (JVM), and Elixir clients&lt;/a&gt; — with a Python client coming soon (no commitment implied) — so the event log is not a .NET-only story.&lt;/li&gt;
&lt;/ul&gt;
</content:encoded><category>chronicle</category><category>event-sourcing</category></item><item><title>Event sourcing in any language: how Chronicle&apos;s gRPC contract works</title><link>https://blog.cratis.io/event-sourcing-in-any-language/</link><guid isPermaLink="true">https://blog.cratis.io/event-sourcing-in-any-language/</guid><description>Chronicle&apos;s kernel sits behind a language-agnostic gRPC/protobuf boundary — 22 canonical .proto contracts that any language can implement. Here&apos;s how the contract is layered, how the existing clients are built on it, and what a new client implements.</description><pubDate>Fri, 28 Aug 2026 12:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Event sourcing libraries are usually tied to one language: the store, the client, and the programming model ship as a single package for a single ecosystem. &lt;a href=&quot;https://cratis.io/chronicle/&quot;&gt;Chronicle&lt;/a&gt; takes a different shape. It is an event-sourcing database and processing runtime with a first-class .NET SDK and additional TypeScript, Kotlin/Java (JVM), and Elixir clients — with a Python client coming soon — and the boundary between the server and every one of those clients is a wire contract, not a language binding.&lt;/p&gt;
&lt;p&gt;This post walks through how that contract works: what the server exposes, how the shipped clients are layered on top of it, and what a new client for another language actually has to implement.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;one-kernel-one-wire-contract&quot;&gt;One kernel, one wire contract&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Chronicle uses a .NET/Orleans actor-based kernel behind gRPC/HTTP surfaces and supports multiple event stores, namespaces, and persistent event-store subscriptions with outbox/inbox sequences. The kernel is where the event-sourcing behavior lives; clients talk to it over the network.&lt;/p&gt;
&lt;p&gt;The contract itself is a set of &lt;strong&gt;22 canonical &lt;code dir=&quot;auto&quot;&gt;.proto&lt;/code&gt; files&lt;/strong&gt; in the Chronicle repository, under &lt;a href=&quot;https://github.com/Cratis/Chronicle/tree/main/Source/Kernel/Protobuf&quot;&gt;&lt;code dir=&quot;auto&quot;&gt;Source/Kernel/Protobuf&lt;/code&gt;&lt;/a&gt;. Together they describe the full client-facing surface: event types and event sequences, event stores and namespaces, observation (reactors, reducers, and event-store subscriptions), projections and read models, jobs, constraints, compliance, identities, recommendations, and the host and client handshake itself.&lt;/p&gt;
&lt;p&gt;Because the boundary is protobuf over gRPC, any language with a gRPC implementation can talk to it. There is nothing .NET-specific on the wire — the .NET SDK is a client of the same contract as everyone else.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;how-every-client-is-layered&quot;&gt;How every client is layered&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Every client Chronicle has shipped follows the same layering, documented in the &lt;a href=&quot;https://cratis.io/chronicle/building-a-client/&quot;&gt;Building a Chronicle Client&lt;/a&gt; guide:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;The canonical &lt;code dir=&quot;auto&quot;&gt;.proto&lt;/code&gt; files&lt;/strong&gt; — owned by the kernel, versioned with it.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;A generated contracts package per language&lt;/strong&gt; — strongly-typed bindings generated from the protos and published as an ordinary package. It is regenerated by Chronicle’s own pipeline on every kernel release and treated as read-only.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;An idiomatic client&lt;/strong&gt; — the hand-maintained package developers actually import. This is where the language’s own idioms live: decorators in TypeScript, annotations on the JVM, processes and supervision in Elixir, attributes in .NET. It depends on the contracts package; its users never have to.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Optional convenience packages&lt;/strong&gt; — hosting integrations such as ASP.NET Core or Spring Boot support, layered above the idiomatic client.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The result is that “event sourcing in language X” doesn’t mean porting a database. It means generating bindings from the same 22 contracts and writing the idiomatic layer that makes them feel native.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;what-a-new-client-implements&quot;&gt;What a new client implements&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Hand-rolling a client means solving the same handful of plumbing problems every existing client has solved before it can do anything useful:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Typed bindings&lt;/strong&gt; generated from the wire contract, kept in a separate contracts package rather than hand-edited.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Authentication&lt;/strong&gt; — parsing the connection string’s authentication modes and exchanging credentials for a token, keeping it fresh.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Connection lifecycle&lt;/strong&gt; — discovering servers, reconnecting when a connection drops.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Contract-version checking&lt;/strong&gt; — rejecting a server whose contract has drifted instead of silently sending it garbage.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;None of that is domain logic, and all of it is written down: the &lt;a href=&quot;https://cratis.io/chronicle/building-a-client/&quot;&gt;Building a Chronicle Client&lt;/a&gt; section of the documentation is the experience of building the TypeScript, Kotlin, and Elixir clients distilled into a checklist, with a page explaining the &lt;em&gt;why&lt;/em&gt; behind each item.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;chronicle-in-your-language-today&quot;&gt;Chronicle in your language today&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Each shipped client has its own landing page with installation and a first taste of the API:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://cratis.io/event-sourcing/dotnet/&quot;&gt;.NET&lt;/a&gt; — the first-class SDK, &lt;code dir=&quot;auto&quot;&gt;Cratis.Chronicle&lt;/code&gt; on NuGet.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://cratis.io/event-sourcing/typescript/&quot;&gt;TypeScript and Node.js&lt;/a&gt; — &lt;code dir=&quot;auto&quot;&gt;@cratis/chronicle&lt;/code&gt; on npm.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://cratis.io/event-sourcing/kotlin/&quot;&gt;Kotlin and Java (JVM)&lt;/a&gt; — &lt;code dir=&quot;auto&quot;&gt;io.cratis:chronicle&lt;/code&gt; on Maven Central, including a Spring Boot starter.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://cratis.io/event-sourcing/elixir/&quot;&gt;Elixir&lt;/a&gt; — &lt;code dir=&quot;auto&quot;&gt;cratis_chronicle&lt;/code&gt; on Hex.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://cratis.io/event-sourcing/python/&quot;&gt;Python&lt;/a&gt; — coming soon: pre-alpha, no package published yet, and no commitment implied.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;All of them are built on the same wire contract: the same 22 canonical &lt;code dir=&quot;auto&quot;&gt;.proto&lt;/code&gt; files, generated into a contracts package for each language, with an idiomatic client layered on top.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;free-and-open-source&quot;&gt;Free and open source&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Chronicle is MIT licensed and free to use, and so is everything Cratis publishes today.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;where-to-go-next&quot;&gt;Where to go next&lt;/h2&gt;&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://cratis.io/chronicle/get-started/&quot;&gt;Get started with Chronicle&lt;/a&gt; — your first event-sourced application.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://cratis.io/chronicle/building-a-client/&quot;&gt;Building a Chronicle Client&lt;/a&gt; — the full guide and checklist for bringing Chronicle to a new language.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://cratis.io/chronicle/architecture/&quot;&gt;Chronicle architecture&lt;/a&gt; — how the kernel is put together.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/Cratis/Chronicle&quot;&gt;Chronicle on GitHub&lt;/a&gt; — the kernel, the contracts, and the .NET SDK.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://cratis.io/community/&quot;&gt;Community and help&lt;/a&gt; — where to ask questions and follow along.&lt;/li&gt;
&lt;/ul&gt;
</content:encoded><category>chronicle</category><category>clients</category></item><item><title>Choosing an event sourcing stack for .NET: an honest comparison</title><link>https://blog.cratis.io/choosing-an-event-sourcing-stack-for-dotnet/</link><guid isPermaLink="true">https://blog.cratis.io/choosing-an-event-sourcing-stack-for-dotnet/</guid><description>Most event sourcing comparisons reduce to popularity. We compiled a version-pinned, source-cited comparison of KurrentDB, Marten, and Cratis Chronicle instead — every row reproducible from public documentation, with the same rules applied to our own product. Here&apos;s how to use it.</description><pubDate>Fri, 28 Aug 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Choosing an event sourcing foundation for a .NET system is a long-lived decision. The event store outlives frameworks, UI rewrites, and often the team that chose it. Yet most comparisons available today reduce to popularity: the tools with the most blog posts win the evaluation before it starts.&lt;/p&gt;
&lt;p&gt;We wanted something more useful — for you and, honestly, for us. So we compiled a comparison where every factual row names the exact versions compared and is reproducible from public documentation and released packages. Where our own product appears, the same rules apply, including the limitations.&lt;/p&gt;
&lt;p&gt;The full matrix lives on cratis.io, and that page is the source of truth: &lt;strong&gt;&lt;a href=&quot;https://cratis.io/compare-event-sourcing-dotnet/&quot;&gt;Event sourcing in .NET: comparing KurrentDB, Marten, and Cratis Chronicle&lt;/a&gt;&lt;/strong&gt;. This post is the walk-through.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;the-rules-we-set-ourselves&quot;&gt;The rules we set ourselves&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Before writing a single row, we fixed the method:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Released, verifiable facts only.&lt;/strong&gt; Every cell is cited from the named tool’s own public documentation, repository, or package-registry listing, at a named version, with the retrieval date recorded.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;No performance, maturity, or superiority claims.&lt;/strong&gt; Not “faster”, not “more mature”, not “production-proven”, not “best” — for any tool, including our own. Those claims need benchmarks and evidence a documentation comparison does not carry.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Caveats stay visible.&lt;/strong&gt; The published page carries its limitations section in full: the matrix compares documentation, not behavior; a listed client package says nothing about feature parity; versions move.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;A refresh promise.&lt;/strong&gt; The matrix is re-verified when a compared tool ships a new major or minor release, changes its license or client list, or 90 days pass. If a compared tool disputes a row we cannot re-verify, we correct or remove it.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If a comparison cannot survive those rules, it is marketing, not a comparison.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;the-candidates-in-one-paragraph-each&quot;&gt;The candidates, in one paragraph each&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href=&quot;https://docs.kurrent.io&quot;&gt;KurrentDB&lt;/a&gt; (formerly EventStoreDB)&lt;/strong&gt; is a purpose-built event store database — events live in streams inside its own storage engine, with official gRPC clients for several languages. You bring your own CQRS layer and read-model infrastructure. It is licensed under the Kurrent License v1, which its own documentation notes is not an OSI-approved open source license.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href=&quot;https://martendb.io&quot;&gt;Marten&lt;/a&gt;&lt;/strong&gt; is a .NET library that turns PostgreSQL into a document and event store. It runs inside your application process, leans on PostgreSQL’s JSON support and ACID compliance, and has rich projection support — inline, async, and live. MIT licensed; PostgreSQL is a prerequisite and .NET is the boundary.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href=&quot;https://cratis.io/chronicle/&quot;&gt;Cratis Chronicle&lt;/a&gt;&lt;/strong&gt; is an event-sourcing database and processing runtime: a separate server with a first-class .NET SDK, released TypeScript, Java/Kotlin (JVM), and Elixir client packages, and a Python client coming soon (pre-alpha, unpublished, no commitment implied). MIT licensed. It pairs with Arc for CQRS and generated TypeScript proxies, and a React component library.&lt;/p&gt;
&lt;p&gt;The &lt;a href=&quot;https://cratis.io/compare-event-sourcing-dotnet/&quot;&gt;canonical matrix&lt;/a&gt; pins the exact versions — KurrentDB server 26.0, Marten 9.30.0, Chronicle 17.0.0 — and cites every row.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;youre-choosing-an-ecosystem-not-only-a-database&quot;&gt;You’re choosing an ecosystem, not only a database&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Here is the part most comparisons skip: none of these tools lives alone. The event store is the center of a decision, but rarely the whole of it — around every store sits the layer you build applications with, the tooling you operate with, and the path events take to the rest of your architecture. Comparing the stores cell by cell and stopping there would miss where much of your time actually goes.&lt;/p&gt;
&lt;p&gt;So the &lt;a href=&quot;https://cratis.io/compare-event-sourcing-dotnet/&quot;&gt;canonical matrix&lt;/a&gt; now carries an ecosystem section, compiled under the same rules — every cell restates the vendor’s own listing, with retrieval dates, and crowns nobody:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;KurrentDB&lt;/strong&gt; documents a server-side Connectors subsystem — pre-installed and enabled by default — that runs catch-up subscriptions and pushes filtered or transformed events to external systems through sinks, with a documented catalog covering Elasticsearch, HTTP, Kafka, MongoDB, RabbitMQ, and Serilog.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Marten&lt;/strong&gt; is one member of JasperFx’s &lt;a href=&quot;https://jasperfx.net&quot;&gt;Critter Stack&lt;/a&gt;, described by its maintainers as one family of .NET tools for event sourcing, document storage, and messaging: Wolverine for messaging, Polecat, Fisher, Weasel, Alba, and CritterWatch alongside Marten itself.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Cratis Chronicle&lt;/strong&gt; is part of the Cratis ecosystem: &lt;a href=&quot;https://cratis.io/arc/&quot;&gt;Arc&lt;/a&gt; for CQRS with generated TypeScript proxies, &lt;a href=&quot;https://cratis.io/components/&quot;&gt;Components&lt;/a&gt; for React, the &lt;a href=&quot;https://cratis.io/cli/&quot;&gt;CLI&lt;/a&gt; and the Web Workbench for operating the store, released clients for .NET, TypeScript, JVM, and Elixir, and free &lt;a href=&quot;https://cratis.io/ai/&quot;&gt;AI skills, rules, and diagnostics&lt;/a&gt; that teach an assistant the platform’s conventions.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;None of this makes any tool better. It changes what you are evaluating. If you pick a store, you are also picking — or committing to build — everything around it: ask what surrounds each candidate, who maintains it, and how much of it you would otherwise write yourself. The ecosystem rows on the comparison page carry citations and retrieval dates so you can verify each listing the same way you verify a storage row.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;how-to-actually-choose&quot;&gt;How to actually choose&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;The matrix deliberately does not crown a winner. Instead, ask fit questions:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Do you want a dedicated event database and your own application layer?&lt;/strong&gt; A purpose-built store with bring-your-own CQRS gives maximum control and maximum assembly work.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Are you a PostgreSQL shop wanting minimum new infrastructure?&lt;/strong&gt; A library-on-PostgreSQL approach is hard to beat for operational simplicity.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Do you want event sourcing, CQRS, and frontend integration designed together?&lt;/strong&gt; An integrated platform trades some flexibility for coherence: projections, read models, generated frontend contracts, and query updates that share one design. That trade-off is real — coherence and lock-in concerns are two views of the same property, and you should weigh both.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Is your organization polyglot?&lt;/strong&gt; Check which client languages are first-party and what each client actually supports. Client existence is not client parity — for any tool, including ours.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Who explains the system at 3 a.m.?&lt;/strong&gt; Compare the operational surfaces: what does each tool show you about subscription progress, failed projections, and replay?&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;None of these questions has a universal answer. A team that answers “PostgreSQL shop, .NET only, minimal infrastructure” is describing Marten’s sweet spot. A team that wants a dedicated event database under a stack they assemble themselves is describing KurrentDB’s. A team that wants the store, the CQRS layer, and the frontend contract designed together is describing Chronicle’s.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;verify-us&quot;&gt;Verify us&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Every row on the &lt;a href=&quot;https://cratis.io/compare-event-sourcing-dotnet/&quot;&gt;comparison page&lt;/a&gt; carries its source and retrieval date precisely so you do not have to take our word for it. Open the cited documentation, check the cell, and if you find one that no longer matches its source, &lt;a href=&quot;https://cratis.io/feedback/&quot;&gt;tell us&lt;/a&gt; — we will re-verify and fix it.&lt;/p&gt;
&lt;p&gt;That is the comparison we wished existed when we started. Use it, argue with it, and hold us to the refresh promise.&lt;/p&gt;
</content:encoded><category>chronicle</category><category>event-sourcing</category></item><item><title>The Cratis ecosystem at a glance</title><link>https://blog.cratis.io/the-cratis-ecosystem-at-a-glance/</link><guid isPermaLink="true">https://blog.cratis.io/the-cratis-ecosystem-at-a-glance/</guid><description>What Cratis is, what Chronicle covers, and how Arc, Components, the CLI, Workbench, and the experimental model-first layer fit together — all open source and MIT licensed.</description><pubDate>Fri, 28 Aug 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Cratis is an open-source, MIT-licensed platform for building event-sourced and CQRS applications. This post is a quick tour of the ecosystem: what each piece does, how the pieces fit together, and where to go to learn more.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;chronicle-the-center-of-the-stack&quot;&gt;Chronicle: the center of the stack&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;At the center is &lt;a href=&quot;https://cratis.io/chronicle/&quot;&gt;Chronicle&lt;/a&gt;, an event-sourcing database and processing runtime with a first-class .NET SDK and additional TypeScript, Kotlin/Java (JVM), and Elixir clients — with a Python client coming soon — plus pluggable storage-provider implementations including MongoDB (default), PostgreSQL, SQL Server, and SQLite.&lt;/p&gt;
&lt;p&gt;Chronicle’s kernel runs on Microsoft Orleans and exposes a language-agnostic gRPC/protobuf boundary, so any language can implement the contract. AI agents can connect to a running Chronicle through the &lt;a href=&quot;https://cratis.io/chronicle-mcp/&quot;&gt;Chronicle MCP server&lt;/a&gt;, regardless of client language.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;building-applications-arc-and-components&quot;&gt;Building applications: Arc and Components&lt;/h2&gt;&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href=&quot;https://cratis.io/arc/&quot;&gt;Arc&lt;/a&gt;&lt;/strong&gt; — an opinionated CQRS application framework for ASP.NET Core: commands, queries, validation, authorization, and TypeScript proxy generation. Arc works without event sourcing, with optional Chronicle integration.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href=&quot;https://cratis.io/components/&quot;&gt;Components&lt;/a&gt;&lt;/strong&gt; — React components for CQRS and event-sourced applications built with Arc: command dialogs, typed forms, and query-backed data tables.&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;&lt;h2 id=&quot;inspecting-and-diagnosing-cli-and-workbench&quot;&gt;Inspecting and diagnosing: CLI and Workbench&lt;/h2&gt;&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href=&quot;https://cratis.io/cli/&quot;&gt;CLI&lt;/a&gt;&lt;/strong&gt; — terminal workflows for inspecting and diagnosing Chronicle: events, observers, projections, read models, and failed partitions.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Workbench&lt;/strong&gt; — the web-based inspection surface for Chronicle event stores.&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;&lt;h2 id=&quot;the-model-first-layer-experimental&quot;&gt;The model-first layer (experimental)&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Cratis also includes an experimental model-first layer, currently in early development:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href=&quot;https://cratis.io/studio/&quot;&gt;Studio&lt;/a&gt;&lt;/strong&gt; — the collaborative environment for designing, visualizing, and editing Screenplay event models (experimental).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href=&quot;https://cratis.io/screenplay/&quot;&gt;Screenplay&lt;/a&gt;&lt;/strong&gt; — a model-first language for event-sourced, CQRS systems — commands, events, projections — rendered by Stage into an Arc + Chronicle application (experimental).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Stage&lt;/strong&gt; — renders Screenplay models into reviewable Arc + Chronicle applications (experimental).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Scene&lt;/strong&gt; — describing a user interface without describing a platform: the UI model of the model-first layer (experimental).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href=&quot;https://cratis.io/prologue/&quot;&gt;Prologue&lt;/a&gt;&lt;/strong&gt; — captures existing system behavior (SQL Server CDC, Postgres logical replication, HTTP, OTLP) into event models for the model-first layer (experimental).&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;&lt;h2 id=&quot;free-and-open-source&quot;&gt;Free and open source&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Everything Cratis publishes today is MIT licensed and free to use.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;where-to-go-next&quot;&gt;Where to go next&lt;/h2&gt;&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://cratis.io/cratis-stack/&quot;&gt;The Cratis Stack&lt;/a&gt; — how the pieces fit together end to end.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://cratis.io/why-cratis/&quot;&gt;Why developers choose Cratis&lt;/a&gt; — the reasoning behind the stack.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://cratis.io/chronicle/get-started/&quot;&gt;Get started with Chronicle&lt;/a&gt; — your first event-sourced application.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://cratis.io/samples/&quot;&gt;Samples&lt;/a&gt; — runnable event sourcing and CQRS samples for the whole stack.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/Cratis&quot;&gt;Cratis on GitHub&lt;/a&gt; — every repository, all open source.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://cratis.io/community/&quot;&gt;Community and help&lt;/a&gt; — where to ask questions and follow along.&lt;/li&gt;
&lt;/ul&gt;
</content:encoded><category>ecosystem</category></item></channel></rss>