Documentation Example manifests
Example manifests
Ready-to-edit manifests, one per framework the tool has been shown to read — each proved by a
test that seeds a real store through that framework's own library and drives the pages over it.
Copy one to the root of the zip you upload as statelens.json, beside the assemblies it names,
and change the four lines that are yours, and add a fifth if a team of its own reads it:
| Line | Change it to |
|---|---|
name |
What the service is called. Its address is made from it: Orders is browsed at /orders |
description |
A sentence for the service's sheet, or delete the line |
assemblies |
The file names of the assemblies in the zip that hold the domain types — events, streams, aggregates, projections and their ids. Only these are scanned |
connectionString |
The name of an entry under ConnectionStrings in the tool's configuration, never the string itself |
roles.read |
Optional: who may read this service beside the global roles — claim values your provider sends, or role names when the tool keeps the accounts. Leave it out for the global roles alone — see Roles |
Everything else in the file describes the framework's conventions and tables, and stays as it is. Each file is offered for download at the foot of this page, and each is a preset in the manifest builder, to change in a form rather than by hand. The Settings page's sheet for the service says all of it back in words once the zip is installed, so a manifest read other than as meant is caught there. Where the framework keeps metadata beside each event, the manifest names that column too, and the event's Json view shows it under the payload.
memoria.json — a store Memoria wrote
For a service built on Memoria over its Entity Framework Core store: the DomainEvents,
DomainAggregates and DomainProjections tables, on PostgreSQL, SQL Server or SQLite.
Set "dcb": true only when the same database also holds the dynamic consistency boundary tables
(DcbEvents, DcbEventTags, DcbSnapshots) — a service that called
AddMemoriaDcbEntityFrameworkCore. That is the one thing the manifest cannot describe, and it
switches the DCB pages on. Leave it false for a streamed-only store, or those pages open on
tables that are not there.
Two things worth knowing:
- Do not zip a
Memoria*assembly. The types bind to the ones the tool already loaded; a copy built against another version loads and then registers nothing. - The connection string only needs to read. The tool writes nothing to any store.
marten.json — a store Marten wrote
For a service built on Marten with string stream identity: the
mt_events and mt_streams tables, and one mt_doc_* table per aggregate Marten snapshots
inline. The example has two aggregates, an order and a loyalty balance; add or remove a pair of
lines per aggregate of your own. Three things are the application's:
- The stream types. Marten keeps a stream key as a string and nothing else, so what a key is
made of is the domain's to say: a record per kind of stream whose
ToString()writes the key —OrderIdwritingorder-{number}in the example — named understreamsand as its aggregate'sid. That is what lets the tool tell one kind of stream from another and work a key back to its values. - The names. Marten names an event type by its class in snake case and an aggregate's document
table by its class in lower case (
mt_doc_order), which is what the twonamingrules say. An application that registered other names with Marten says those instead. - The wrapped applies. A Marten aggregate may take an event as
IEvent<OrderPlaced>rather than bare, to read its version or its time. Thefold.wrapperblock is what lets the tool call such a method: it constructs Marten's ownJasperFx.Events.Event<T>around each event — the zip carriesJasperFx.Events.dllbeside the domain — and fills itsVersion,Sequence,TimestampandStreamKeyfrom the stored row. Methods taking the bare event work as before, with or without the block; a domain with no wrapped applies can drop it. Marten's olderMarten.Events.IEvent<T>(before Marten 8) is the same shape underMarten.Events.Event. - The snapshots. Each inline snapshot is a document table, joined to
mt_streamsfor the stream's version and creation. Marten writes the aggregate'sVersioninto the document, which is where theversionexpression reads it; the stream's version is how far the fold reached. A Marten application that keeps no inline snapshots leavessnapshotsout, or declares its own projection tables the way the Eventuous example does.
Archived events are left out by the where; drop it to see them. Marten's timestamp column
carries a zone, so it is read as it is. Marten writes no metadata unless the application opted in
through Events.MetadataConfig; an application that enabled headers adds "metadata": "headers"
to events, and one that enabled correlation and causation alone can show both with an expression
such as "metadata": "jsonb_build_object('correlation_id', correlation_id, 'causation_id', causation_id)".
eventuous.json — a store Eventuous wrote
For a service built on Eventuous over its PostgreSQL store: the
messages and streams tables under the schema the application configured (eventuous unless
it chose another — change both names in the sql if it did). Beside the four lines above, three
things are the application's:
- The stream type. Eventuous names a stream
{Aggregate}-{id}in itsStreamNameMaprather than in a type. Give the domain a record whoseToString()writes that name —BookingStreamin the example — and name it understreams; that is what lets the tool tell one kind of stream from another and work a stored name back to its id. - The state. Name the state record —
BookingState : State<BookingState>— underaggregates, with the aggregate'sIdrecord as itsid. The fold callsWhen, which the framework'sState<T>declares, and takes the state each call returns. - The read model. Eventuous keeps no snapshots: an aggregate is always folded from its stream,
and what the application keeps in tables is whatever its projectors write. Declare each such
table under
snapshots, withtypeNamenaming the state it holds, or leavesnapshotsout for a store with none. Thebooking_summaryin the example is one such table, with the state as JSON, the count of events folded, the last stream position, and when.
Events are named by Eventuous's own [EventType("V1.RoomBooked")], which is what the naming
rule reads. created is a timestamp without a zone, so the sql puts UTC on it.
messagedb.json — a store Message DB wrote
For a service built on Message DB — Eventide's
PostgreSQL message store — where everything is one table, message_store.messages, and streams
are told apart by their category. Beside the four lines above, the manifest names the entity's
category, account in the example, in three places: the where on the events, the snapshot
SELECT, and the stream name the SELECT puts back together. Three things are the application's:
- The stream type. Eventide composes a stream name as
{category}-{id}, and Message DB keeps the rule in itscategory()andid()functions rather than in a type. Give the domain a record whoseToString()writes the name —AccountStreamin the example — and name it understreams. - The events. Eventide writes a message under its class name, which is what
class-namereads, andcategory(stream_name) = 'account'keeps the entity's messages apart from its snapshot stream's.timehas no zone, so the manifest puts UTC on it in place. - The snapshots. Message DB has no table of them. An Eventide component records an entity's
snapshot as one more message, of type
Recorded, in the entity's snapshot stream —account:snapshot-123besideaccount-123— carrying the entity's data and the version it was at. The snapshotSELECTtakes the latestRecordedmessage per snapshot stream, works the entity's stream and id back out of the name through Message DB's own functions, and reads the version and the data out of the message. Change the two JSON paths if the application records its snapshots under other names, or leavesnapshotsout for a component that records none.
sqlstreamstore.json — a store SQL Stream Store wrote
For a service built on SQL Stream Store over
its PostgreSQL store: the messages and streams tables under the schema the application
configured (public unless it chose another — change both names in the sql if it did). SQL
Stream Store is events only: it keeps no snapshots and projects nothing, so the manifest has no
snapshots at all, and the pages that list them say the store holds none. Two things are the
application's:
- The stream type. The library keeps a stream id as a string and nothing else. Give the domain
a record whose
ToString()writes it —OrderIdwritingorder-{number}in the example — and name it understreamsand as the aggregate'sid. - The type strings. The library stores whatever string the application passed for each
message's type; the example's application passed the class name, which is what
class-namereads. An application that passed other strings uses amap.
The messages table holds a stream number, so the sql joins the streams table for the id the
application wrote; the library's own streams, named with a leading $, are left out; and the
zoneless created_utc is given its zone.
See What to put in a zip for every key.
Back to topsqlserver.json — a home-grown store on SQL Server
For a service with no framework at all: plain records for the events, an aggregate with Apply
methods and a Version, and tables the application designed itself on SQL Server. The example's
tables are dbo.Events, one snapshot table per aggregate and a summary table; rename the columns
to yours, and the domain types to yours. The application calls its read models summaries rather
than projections, and labels says so — one summary, many summaries, given as both because the
plural is not the word with an s — so the tool's pages say Summaries where they would say
Projections; delete the line to keep the tool's words. Unlike the other examples this one is not proved by a
container test — the suite has no SQL Server — so it is checked only as a manifest the tool
accepts; the same manifest over SQLite is what MappedStoreTests proves. Three things worth
knowing on this engine:
- Dates must come back with a zone. The tool reads
written,createdandupdatedasdatetimeoffset. A column of that type is read as it is; adatetime2holding UTC is wrapped, as the example does withTODATETIMEOFFSET(AppendedAt, 0). A column with no zone left bare is read as nothing. - Identifiers are bracketed for you. A plain name —
dbo.Events,StreamName— is quoted part by part in SQL Server's own brackets; anything that is not a plain name is taken as an expression and passed through as written. - Text is
nvarchar(max). The payload, the type and the stream id are cast to it whatever column type holds them, so avarcharor a JSON column both read.
See What to put in a zip for every key.
Back to topkurrentdb.json — a store in KurrentDB
For a service that writes to KurrentDB (formerly EventStoreDB), with
or without a framework: a store with no tables, so the block is of another kind, kurrentdb, and
the connection string is the client's own URL, esdb://host:2113?tls=false or kurrentdb://….
Proved by a test that seeds the last EventStoreDB image through the client and drives the pages
over it. Three things are the application's:
- The stream names. The events source names the prefix, or prefixes, the service's streams
are named with —
order-in the example, soorder-42is one order — and the domain gives a record whoseToString()writes such a name, named understreamsand as the aggregate'sid. - The type strings. KurrentDB stores whatever string the application passed for each event's
type; the example passed the class name, which is what
class-namereads. An application that passed other strings uses amap. - The snapshots, if any. An application that snapshots writes each model's state as an event
in a stream of its own —
snapshot-order-42in the example — and the source says where in that event the version and the state are, and how the model's own stream is named from the id. An application that keeps no snapshots leavessnapshotsout; its aggregates page then says the store holds none, and the events and streams read all the same. The snapshot prefix must not be a prefix of the event streams' names, or the snapshots would be listed among the events.
See A store in KurrentDB for every key.
Back to topcosmos.json — a store in Cosmos DB
For a service that keeps its events as documents in Cosmos DB, with or without a framework: the
same block as a relational store with "kind": "cosmos", the database, and a container in
place of each table. Proved by a test that seeds the Cosmos DB emulator through the SDK with a
home-grown layout — a document per event with its payload as a nested document, a snapshot document
per order in a second container — and drives the pages over it. Three things are the application's:
- The properties. Each column is a property of the document, plain or as a path, or an
expression in Cosmos's SQL over
c. The example's events keep the payload inbody, a nested document, and the metadata inmeta; a payload kept as a JSON string reads the same. - The dates.
at,createdAtandupdatedAtare ISO text in the example. A store that relies on Cosmos's own_tswrites"written": "c._ts", which is read as seconds since the epoch. - The snapshots. A container of the latest state per model, with the version and the sequence
read up to as properties. A store that keeps events and snapshots in one container tells them
apart with a
where, as the next example does.
cosmos-memoria.json — a store Memoria wrote to Cosmos DB
For a service built on Memoria's own Cosmos store: one container, Domain in the database
Memoria by default, holding events, aggregates and projections told apart by documentType.
The domain block is memoria.json's; the store block names the one container three times, each
time with the where that picks its kind of document. Change database and container to what
the application configured.
See A store in Cosmos DB for every key.
Back to top