Skip to content

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.

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.

Chronicle’s TypeScript, Kotlin/Java, and Elixir clients follow the layering documented in the Building a Chronicle Client guide:

  1. The canonical .proto files — owned by the kernel, versioned with it.
  2. 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.
  3. 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.
  4. 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.

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.

Each shipped client has its own landing page with installation and a first taste of the API:

  • .NET — the first-class SDK, Cratis.Chronicle on NuGet.
  • TypeScript and Node.js — @cratis/chronicle on npm.
  • Kotlin and Java (JVM) — io.cratis:chronicle on Maven Central, including a Spring Boot starter.
  • Elixir — cratis_chronicle on 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.

Chronicle is MIT licensed and free to use, and so is everything Cratis publishes today.