Your first command and query
Let’s build the backend half of a real feature: registering an author in a library app. It’s small, but it exercises the whole Arc loop — something happens (a command), it writes state, and that state is served back out (a query). We use a plain database in this lesson to keep the CQRS boundary visible; in a full Cratis information system, the same boundary usually writes events through Chronicle.
In a layered app this would be files scattered across Commands/, Handlers/, and ReadModels/, and you’d hop between them to follow one behavior. Arc organizes by feature instead: everything below lives in one Features/Authors/ folder you read top to bottom. Here’s the shape we’re building:
Prerequisites
Section titled “Prerequisites”- .NET SDK 8 or newer.
- An Arc backend project — add the
Cratis.Arcpackages to an ASP.NET Core app, or scaffold the full stack with the Cratis templates. - A database Arc can write to — MongoDB, or EF Core over SQLite/Postgres. Chronicle is not required for this lesson.
-
A strongly-typed id. Never pass raw
Guids around the domain — wrap them so the compiler keeps them straight and your signatures document themselves:public record AuthorId(Guid Value) : ConceptAs<Guid>(Value){public static AuthorId New() => new(Guid.NewGuid());} -
The command — with
Handle()on the record. A command is arecordmarked[Command]. The behavior lives in aHandle()method on the record itself — there’s no separate handler class to find. Here it writes the new author to the database:[Command]public record RegisterAuthor(AuthorId Id, AuthorName Name){public Task Handle(IMongoCollection<Author> authors) =>authors.InsertOneAsync(new Author(Id, Name));}Handle()returnsTask— the write is the outcome. Need a different store or a collaborator? Add it as a parameter and Arc injects it: anIMongoCollection<T>, your EF CoreDbContext, a service, anything in the container. -
The read model and its query. Declare the shape you want to query and mark it
[ReadModel]. A static method exposes the query — return an observable so consumers get live updates:[ReadModel]public record Author([property: Key] AuthorId Id, AuthorName Name){public static ISubject<IEnumerable<Author>> AllAuthors(IMongoCollection<Author> collection) =>collection.Observe();} -
Build.
Terminal window dotnet buildBuilding generates the TypeScript proxies for
RegisterAuthorandAllAuthorsso your frontend can call them type-safely — that’s the part that makes the next step (the UI) effortless.
What you built
Section titled “What you built”In one folder, read top to bottom:
- A
[Command]withHandle()— intent and implementation in one place, no handler class. - A
[ReadModel]with a live query method served over HTTP.
That’s a complete vertical slice, over a plain database. The next feature is another folder just like it.
Where to go next
Section titled “Where to go next”- Wire a UI to it — read this query and run this command from React, fully typed.
- Build the library, full-stack — the threaded tutorial builds this into a real app, chapter by chapter.
- Go deeper on Commands and Queries.