Documentation Try it with sample data
Try StateLens with sample data
On this page
StateLens shows you a store through your own domain types. To try it before you have either — or to see what a store with something interesting in it looks like — two things ship beside the tool:
- The seeder,
statelens.azurecr.io/statelens-samples: an image that fills a store with an ecommerce domain modelled once in each consistency model, written through the framework itself. It is in the same private registry as the tool, and the same pull token pulls it — see The container image for thedocker login - The sample archives, published with each release on the public
Memoria repository: one
zip per model, each holding the domain assembly the seeder wrote through and the
statelens.jsonthat declares it, ready to upload
The types the tool displays are the types the seeder exercises, so nothing reaches the tool that was never written through Memoria first.
Four steps, and about five minutes:
1. Fill a store
The seeder writes to whichever store ConnectionStrings__Memoria names. Any of the four engines
works, and the string itself says which one — the resolution is the tool's own, compiled into
both, so a string that reaches one reaches the other; see
which engine it is for the keywords and for
Database__Provider. Unlike the tool, the seeder creates what is missing before it writes: the
database and each store's tables on a relational engine, the database and the container on Cosmos.
The quickest store is a SQLite file on a volume, which the tool's container mounts too in the next step:
docker run -i --rm -v statelens-store:/store \
-e ConnectionStrings__Memoria="Data Source=/store/samples.db" \
statelens.azurecr.io/statelens-samples:1.0.0
For a store that is already running, name it instead. The name is resolved from inside the
container, so a database on the machine itself is host.docker.internal rather than localhost:
| Engine | ConnectionStrings__Memoria |
|---|---|
| PostgreSQL | Host=host.docker.internal;Port=5432;Database=memoria_samples;Username=postgres;Password=… |
| SQL Server | Server=host.docker.internal,1433;Initial Catalog=memoria_samples;User Id=sa;Password=…;TrustServerCertificate=True |
| SQLite | Data Source=/store/samples.db, on a volume mounted at /store |
| Cosmos DB | AccountEndpoint=https://host.docker.internal:8081/;AccountKey=<emulator key> |
For Cosmos, set the database and container as well, Database__Cosmos__DatabaseName and
Database__Cosmos__ContainerName, and start the emulator first — the run stops with "Could not
reach the database" before asking anything otherwise. The tool then reads what the seeder wrote
through a manifest of kind cosmos — see Limits.
The run says what it found and what it bound — here against a PostgreSQL database:
StateLens.Samples
store : Npgsql
writing : memoria_samples
bound : 30 events, 5+5 aggregates, 4+6 projections (streamed+dcb)
schema : installed
It then asks what to do, answered by number on standard input — which is what the -i above is
for:
| Answer | Action |
|---|---|
1 |
Add — writes alongside whatever is already there |
2 |
Replace — empties the store, then writes |
3 |
Delete — empties the store and writes nothing |
| Esc | Quit; the run ends without touching another row |
Then, only when the store holds both models, which data to write: 1 streamed, 2 DCB, 3
both. A Cosmos store holds the streamed model only, so it is never asked there.
Then, for anything that writes, how many of each item: a number, or enter for a random handful. One number covers every kind the run writes — customers and reviewed products on the streamed side, products, orders and suppliers on the DCB side. What hangs off each of them is still as many as the seeding feels like.
The menu comes back when the operation has finished, so one run can add, look at the store, and replace or delete without being started again. Esc at any of the questions ends it.
End of input counts as Esc — it means nobody is there to answer — so a piped run works and an unattended one stops rather than hanging:
printf '1\n3\n25\n' | docker run -i --rm -v statelens-store:/store \
-e ConnectionStrings__Memoria="Data Source=/store/samples.db" \
statelens.azurecr.io/statelens-samples:1.0.0
Exit code 0 means the run finished, including a run where nothing was chosen; 1 means the store
could not be reached and nothing was written.
What it writes
Streamed — customers, the orders they place, and the reviews products collect. Every order is
driven through the Order aggregate's own methods, so the log holds only sequences the domain would
have allowed. Some reviews go through ProductReviewV1 rather than ProductReview, so the store
ends up holding snapshots of two shapes of one model side by side.
DCB — a catalogue, stock for it, orders holding some of that stock, and the purchase orders that restock it. Every append follows the read-decide-append cycle on condition that the boundary has not moved, the way an application would write it.
Snapshots are deliberately left in three states, because a store where everything is current has nothing to demonstrate:
| State | How it is reached |
|---|---|
| Up to date | Snapshotted after the last event on its stream |
| Behind | Snapshotted, then more events appended without snapshotting again |
| No snapshot | Events appended and never snapshotted |
The run ends by listing every model it wrote and where its snapshot stands, so you know which rows the tool will say are behind their stream:
store kind model identifier values snapshot
streamed aggregate Order OrderId o-o5lwx2 v4, up to date
streamed aggregate CustomerAccount CustomerAccountId c-o5lwx1 v2 of 6 — 4 behind
streamed projection OrderSummary OrderSummaryId o-o5lwxc no snapshot — 2 events waiting
Back to top
2. Run the tool over it
The tool's image, open, over the same store: the same volume mounted at the same path, or the same
connection string. The string is given under the name Memoria, which is the name the sample
archives' manifests read it by.
docker run -p 8080:8080 -v statelens-data:/data -v statelens-store:/store \
-e Authentication__Disabled=true \
-e ConnectionStrings__Memoria="Data Source=/store/samples.db" \
statelens.azurecr.io/statelens:1.0.0
Then open http://localhost:8080. See Run it locally for the
shape of this, and Running it open for why it is for localhost
alone.
3. Download the archives
The two zips are attached to the release named after the tool's version on the public Memoria
repository, statelens-v1.0.0 for 1.0.0:
Each holds one .dll and its statelens.json at the root, and nothing else: a zip carrying a
Memoria* core assembly would be refused — see
what to put in a zip. Each declares one service —
Samples Streamed, browsed at /samples-streamed, for the streamed model, and Samples DCB,
at /samples-dcb, for dynamic consistency boundaries — over the Memoria connection string. Two
archives rather than one, each declaring a service of its own, so that you can upload one model
alone; the home page lists whichever are installed, and each service's page is laid out for the
model it registered types under.
Take them from the release matching the images you run. The assemblies are compiled against the framework version the tool reads through; one built against another version still loads, and then contributes no types at all.
Back to top4. Upload them and look around
Go to Settings → Installed → Upload, choose a zip, and upload it — then the other, if you want
both models. The page reports what registered; Settings → Types counts it per model. Nothing
restarts. Each row of the installed table opens a sheet over its zip: the service the manifest
declares, Samples Streamed or Samples DCB, the address it is browsed at, the Memoria
connection string it reads over, and the types registered from the assembly. Home then lists
the services installed; open one, and its page leads into its events, aggregates and projections —
at /samples-streamed/streamed/... and /samples-dcb/dcb/....
Things worth opening first:
- Streamed → Aggregates → Data. Two versions of
ProductReviewsit here under one name, because some reviews were written throughProductReviewV1. The version column is what tells them apart. - A row the seeder left behind. Open it: the Info tab says how far behind its stream the snapshot is, the Events tab lists the events it has not applied yet, and Compare folds the stream on the page to show what the model would be — without writing anything back.
- A row with no snapshot at all. The compare tab folds one from scratch, and says beside each version that no snapshot is stored.
- DCB → Events → Data. Filter by a tag rather than by a stream: a DCB event belongs to no stream, so its tags are the handle a boundary finds it through.
- DCB → Aggregates → Data. Each row is a snapshot keyed by the boundary that produced it. The tags column is that boundary, whole.
Limits
Cosmos holds the streamed samples only. The seeder writes the streamed model to a Cosmos
account and never the DCB one, since there is no Cosmos boundary store; and the tool reads it
through a manifest of kind cosmos — the sample zip's statelens.json declares tables, so over
Cosmos swap its store block for the one in
cosmos-memoria.json,
with the database and container the seeder was given. The DCB pages are not offered under such a
service.
In-memory SQLite is refused. Data Source=:memory: lives only as long as the connection that
opened it, and both the seeder and the tool open a connection per unit of work — they would find an
empty store rather than the one that was seeded. Point them at a file.
Do not upload the DCB samples alongside Memoria.Examples.Ecommerce.Dcb. Both claim the event
types ProductCreated, ProductDeleted and ProductDetailsChanged at version 1, and the DCB
aggregate Product at version 1. Whichever loses the name loses its bindings, and its pages then
report no events inside the boundary. Remove one archive before uploading the other.
Related
- StateLens — what each page shows
- StateLens: configuration