1. Key concepts
Before writing code, learn the words Riverbase uses everywhere: in macros, routes, policy rows, and audit records.
Domain, namespace, and names
A domain is a bounded context. It exposes write operations (commands), read operations (queries), and optional custom routes, all under one namespace.
| Term | Example | Meaning |
|---|---|---|
| Namespace | riverbase.todo | Dotted domain identifier |
| Command key | create-todo | Local, dash-separated name in the command registry |
| Resource | todo | The collection or object type commands and queries target |
| Fully qualified name | riverbase.todo/create-todo | namespace/key, via FqName |
| Integration topic | riverbase.todo.created | Name of a message published to the bus |
Command keys are verb-noun names (create-todo), never resource:verb.
State-based DDD and CQRS
Riverbase keeps aggregate state as the source of truth. It does not use event sourcing:
- Commands request authoritative state changes through the command engine.
- Queries and reports read through the query engine and never mutate state.
- Events and activities are audit and integration facts. Nothing replays them to rebuild state.
DDD and state-based CQRS explains why.
Engines and the domain bundle
| Slot | Declared with | Role |
|---|---|---|
| Command | command_engine! | Write path: collection (:post) and object (:exec) commands |
| Query | query_resource! + query_engine! | Read path: .list, .item, .meta, and .rept reports |
| Service | a type implementing DomainServiceEngine | Optional capabilities outside CQRS HTTP, such as ping |
| Domain | domain! | Namespace metadata, engine slots, spawn, optional custom routes |
domain! generates a type such as TodoDomain<S> that implements the Domain trait. S is the
store type, usually PgDataStore.
Aggregate and EngineContext
- An aggregate is the command session for one dispatch. Your type, such as
TodoAggregate, wrapsAggregateCore, which loads the root row, writes rows, and buffers activities and messages. - Domain methods that change state are marked
#[domain_action(event = "…")]. When the method returnsOk, Riverbase records one event for it. EngineContexttravels with every command and query: namespace, actor, profile, tenant, roles, server-resolved claims, correlation id, and idempotency key.
One store for writes and reads
Command and query engines share one DataStore. In the todo service it is a PgDataStore with the
todo_items entity registered. A per-domain DomainStoreSpec declares the resources, entities, and
migrations. Earlier versions split this into StateStore and DataStore. The string-and-JSON state
helpers now live on DataStore.
The HTTP surface
Routes are generated from the registered commands and resources. The default api_base is /api.
| Operation | Pattern | Example |
|---|---|---|
| Collection command | POST /{api_base}/{ns}/{cmdkey}:post/{resource} | POST /api/riverbase.todo/create-todo:post/todo |
| Object command | POST /{api_base}/{ns}/{cmdkey}:exec/{resource}/{id} | POST /api/riverbase.todo/update-todo:exec/todo/{id} |
| List | GET /{api_base}/{ns}/{resource}.list | GET /api/riverbase.todo/todo.list |
| Item | GET /{api_base}/{ns}/{resource}.item/{id} | GET /api/riverbase.todo/todo.item/{id} |
| Metadata | GET /{api_base}/{ns}/{resource}.meta | Fields, labels and sort options |
| Report | POST /{api_base}/{ns}/{report}.rept | Parameterized report; parameters in the body |
The full list, including scoped, :link, and :hook routes, is in
HTTP conventions.
Authentication, authorization, row policy
Three separate checks happen at different layers:
| Layer | Question | Mechanism |
|---|---|---|
| Authentication | Who is calling? | An auth provider: MockAuth locally, Keycloak/OIDC JWTs, or sessions |
| Authorization | May they run this operation? | Casbin on each route: {cmdkey}.execute, {resource}.query |
| Row and tenant policy | Which rows may they see or change? | The _tenant filter plus each resource's scope_policy |
Authentication alone never grants permission. An empty Casbin policy denies everything.
Composing the application
RiverbaseApp, from riverbase_http, is the entry point:
RiverbaseApp::initloadsriverbase.tomland env config and sets up logging.DomainRuntime::from_configconnects the database, audit log, message bus, and invokers.TodoDomain::spawn(&runtime)starts the engines.app.mount_domain(domain)registers command, query, and custom domain routes.app.mount_auth_routes()adds/api/auth/*when auth is configured.app.serve()binds and runs axum.
Next: 2. Scaffold and domain.