Event sourcing in any language: how Chronicle's gRPC contract works
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. Chronicle takes a different shape. It is an event-sourcing database and processing runtime with a first-class client SDK for .NET and additional TypeScript, Kotlin/Java (JVM), and Elixir clients — and the boundary between the server and every one of those clients is a wire contract, not a language binding.
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.
One kernel, one wire contract
Section titled “One kernel, one wire contract”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.
The contract itself is a set of 26 canonical .proto files in the Chronicle repository, under Source/Kernel/Protobuf. 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.
Because the boundary is protobuf over gRPC, any language with a gRPC implementation can talk to it. Chronicle’s client SDK for .NET uses the same contract as everyone else. A client still has to honor its value encodings: for example, the append envelope uses protobuf-net’s .bcl.Guid for CorrelationId, while EventSourceId is a string.
Take an append: the client sends the event-store and event-source identifiers, event-type metadata, and serialized content in an AppendRequest. The server returns a command result containing the append outcome, including success or violation details. A successful append does not mean that every projection or reactor has finished processing it. The append documentation and sequences.proto describe the two sides of that exchange.
How every client is layered
Section titled “How every client is layered”Chronicle’s TypeScript, Kotlin/Java, and Elixir clients follow the layering documented in the Building a Chronicle Client guide:
- The canonical
.protofiles — owned by the kernel, versioned with it. - A generated contracts package per language — 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.
- An idiomatic client — 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.
- Optional convenience packages — hosting integrations such as ASP.NET Core or Spring Boot support, layered above the idiomatic client.
The .NET client has a different build and packaging path: Chronicle exports its C# contract surface as .proto files, while the .NET client references the C# contracts project and packages those contracts internally. The layering guide explains that exception.
The result is that “event sourcing in language X” doesn’t mean porting a database. It means generating bindings from the same contracts and writing the idiomatic layer that makes them feel native.
What a new client implements
Section titled “What a new client implements”Hand-rolling a client means solving the same handful of plumbing problems every existing client has solved before it can do anything useful:
- Typed bindings generated from the wire contract, kept in a separate contracts package rather than hand-edited.
- Authentication — parsing the connection string’s authentication modes and exchanging credentials for a token, keeping it fresh.
- Connection lifecycle — discovering servers, reconnecting when a connection drops.
- Contract compatibility — checking whether the server can serve the client’s expected contract. Chronicle’s structural check accepts additive changes and reports incompatible ones; it is not simply a version-equality test.
None of that is domain logic, and all of it is written down: the Building a Chronicle Client section of the documentation is the experience of building the TypeScript, Kotlin, and Elixir clients distilled into a checklist, with a page explaining the why behind each item.
Chronicle in your language today
Section titled “Chronicle in your language today”Each shipped client has its own landing page with installation and a first taste of the API:
- .NET — the first-class SDK,
Cratis.Chronicleon NuGet. - TypeScript and Node.js —
@cratis/chronicleon npm. - Kotlin and Java (JVM) —
io.cratis:chronicleon Maven Central, including a Spring Boot starter. - Elixir —
cratis_chronicleon Hex. - Python — in development: the idiomatic client is pre-alpha and not yet published.
All of them are built on the same wire contract: the same 26 canonical .proto files, with an idiomatic client layered on top. What each client exposes on top of that contract can differ — check its own documentation for the features you need.
Free and open source
Section titled “Free and open source”Chronicle is MIT licensed and free to use, and so is everything Cratis publishes today.
Where to go next
Section titled “Where to go next”- Get started with Chronicle — your first event-sourced application.
- Building a Chronicle Client — the full guide and checklist for bringing Chronicle to a new language.
- Chronicle architecture — how the kernel is put together.
- Chronicle on GitHub — the kernel, the contracts, and the .NET SDK.
- Community and help — where to ask questions and follow along.