Project layout
A Riverbase application keeps domain logic and process wiring in separate crates. The domain crate knows nothing about HTTP binding, ports, or config files. The service crate is a thin binary that loads configuration, builds a runtime, and mounts domains.
my-app/
├── Cargo.toml # workspace: members + [workspace.dependencies]
├── domain/ # todo-domain: the bounded context
│ ├── Cargo.toml
│ ├── migrations/ # Diesel migrations for this domain's tables
│ └── src/
│ ├── lib.rs # re-exports
│ ├── domain.rs # domain! — namespace, engine slots, spawn
│ ├── command.rs # command_engine! — payloads, handlers, typed helpers
│ ├── aggregate.rs # TodoAggregate — #[domain_action] methods
│ ├── query.rs # query_resource!, report_resource!, query_engine!
│ ├── service.rs # optional service engine (non-CQRS capabilities)
│ └── postgres/
│ ├── schema.rs # diesel table! with shared domain fields
│ ├── entity.rs # domain_row! + pg_domain_entity!
│ ├── migrate.rs # embedded migrations
│ └── mod.rs # DomainStoreSpec for the domain
└── service/ # todo-service: the binary
├── Cargo.toml
├── riverbase.toml # service defaults (env vars override)
└── src/main.rs # RiverbaseApp::init → DomainRuntime → spawn → mount → serve
This is the layout the Tutorial builds. The
Quick start keeps the same files in one crate, which is fine while there is one
domain. A split deployment adds one more binary crate (split/) that mounts the same domain as two
processes. See Deployment.
Start a new domain
Copy the quick start's files into a new domain crate and rename the resource. Every domain needs the
same six pieces: a query resource, a command engine, an aggregate, a Diesel entity with its
migration, a store spec, and the domain! declaration.
The quick start declares policy: public(...) because it has no tenants. Replace it with a real row
policy before production. See Multi-tenancy.
Dependency direction
Keep it one-way:
- The domain crate depends on
riverbase_coreandriverbase_proc. Gate anything that needs axum (RxDB collections, Casbin helpers) behind an optionalhttpfeature, astodo-domaindoes. - The service crate depends on
riverbase_httpand the domain crates it mounts. - Domains never depend on each other's crates. Cross-domain calls go through the command invoker or integration messages. See Domains and namespacing.
Next: Configuration.