Event sourcing · CQRS · Rust kernel
Business logic in any language. One kernel.
kernable stores, signs and orders every command and event. Your services decide, in any language.
curl -fsSL https://kernable.com/install.sh | shSoon
What the kernel does for you.
Your code only decides. The rest is built in.
- 01
Polling until the read side catches up→Read your own writes - 02
An outbox and a message broker→The kernel delivers - 03
Audit tables you hope are complete→The log is the audit - 04
One language for every team→Any language - 05
Trust us→kernable verify
Storage, ordering, trust and delivery in one process.
Your processes hold the business logic and connect over gRPC. The TypeScript, Python and Rust SDKs share one API.
Takes a command, looks at the aggregate's state, emits events or rejects.
Builds tables from events and answers queries.
Acts on events: sends mail, calls other systems, submits commands.
Authorizes every command and query. Fails closed.
- StoreAn append-only log on SQLite or a file, with snapshots and a vault.
- SignEvery command, result and query is signed. Events form a hash chain.
- OrderOne command per aggregate at a time. Listeners always run in a fixed order.
- DeliverStraight to every connected process. You don't need a separate broker.
- One binary and a SQLite file. No broker, no cluster.
- Built-in UI and metrics. See every service, event and aggregate live.
- Crash-tested. Aborted at every crash point and fuzzed on damaged logs.
A shopping cart in three files.
Submit a command and read the result straight away, with no polling.
import { Kernable, Rejected, domain } from "@kernable/sdk";
const cart = domain("shop").aggregate("cart", { items: 0, checkedOut: false });
cart.apply("ItemAdded", (state, data: { qty: number }) => { state.items += data.qty; });
cart.apply("CheckedOut", (state) => { state.checkedOut = true; });
cart.command("AddItem", (state, data: { sku: string; qty: number }) => {
if (state.checkedOut) throw new Rejected("Cart is checked out");
return [{ type: "ItemAdded", data }];
});
cart.command("CheckOut", (state, _data, _cmd, ctx) => {
if (state.items === 0) throw new Rejected("Cart is empty");
// ctx.now() and ctx.id() stay the same if the command runs again.
return [{ type: "CheckedOut", data: { at: ctx.now().toISOString(), orderId: ctx.id() } }];
});
await new Kernable({ key: "app.key" }).worker(cart).run();
import { Kernable, Rejected, domain } from "@kernable/sdk";
const carts = domain("shop").readModel("carts");
const rows = new Map<string, { items: number; order?: string }>();
carts.on("ItemAdded", (data: { qty: number }, evt) => {
const row = rows.get(evt.aggregateId) ?? { items: 0 };
row.items += data.qty;
rows.set(evt.aggregateId, row);
});
carts.on("CheckedOut", (data: { orderId: string }, evt) => {
rows.get(evt.aggregateId)!.order = data.orderId;
});
carts.query("GetCart", (data: { id: string }) => {
const row = rows.get(data.id);
if (!row) throw new Rejected("no such cart", "not_found");
return row;
});
await new Kernable({ key: "app.key" }).listener(carts).run();
import { Kernable } from "@kernable/sdk";
const kernable = new Kernable({ key: "app.key" });
const cartC1 = { domain: "shop", aggregateType: "cart", aggregateId: "c1" };
await kernable.submit({ ...cartC1, type: "AddItem", data: { sku: "apple", qty: 2 } });
// submit returned only after every listener processed the event: no polling needed.
let cart = await kernable.query({ domain: "shop", type: "GetCart", data: { id: "c1" } });
// { items: 2 }
await kernable.submit({ ...cartC1, type: "CheckOut" });
const outcome = await kernable.submit({ ...cartC1, type: "AddItem", data: { sku: "pear", qty: 1 } });
// { committed: false, code: "rejected", message: "Cart is checked out" }
Every code example in the documentation runs against a real kernel in CI.
An application kernel, not another piece to wire up.
| kernable | Database + outbox + bus | Event store databases | Event sourcing frameworks | Logs and streams | |
|---|---|---|---|---|---|
| Business logic runs | in your processes, any language | in your services | in your application | on the framework's platform | in your consumers |
| Read your own writes | built in | you build it | you build it | depends on the setup | you build it |
| Signed, verifiable history | built in | not built in | not built in | not built in | not built in |
| Operating it | one binary and a file | a database and a message bus | a database server or cluster | a server or cluster | a cluster |
Where kernable is young: one kernel per store with no failover yet, no compaction, and the API may change before 1.0.
Open core. It stays open.
The core is Apache-2.0, and that will not change.
Community
Free
- The Rust kernel, event store and vault
- SDKs for TypeScript, Python and Rust
- The CLI, the built-in UI and metrics
Enterprise
On request
- Tenancy, identity and RBAC, ready-made
- Single sign-on and multiple nodes
- Long-term support builds and support plans
[ Early access ]
Build on kernable before 1.0.
kernable is in private preview. Tell us what you're building, and we'll send you the binaries and the docs.