Architecture
Riverbase is a modular kernel plus focused capability crates. The kernel, riverbase_core,
holds everything a domain needs to accept commands and serve queries. riverbase_http turns
domains into an axum portal. Extension crates add workers, media files, and forms without growing
the kernel.
Crate graph
Edges only point toward the kernel. A new edge between crates is an architecture change, not a convenience.
Inside riverbase_core
| Module | Responsibility |
|---|---|
base | IDs, names, scopes, EngineContext, errors, aggregate and audit context, tenant policy |
command | The ractor command engine, command metadata, typed handlers, the aggregate session, the message-bus port, process managers |
query | Query and report resources, interface and binding, lowering to DataQuery, the query engine |
domain | The Domain trait, engine traits and composites, DomainRuntime, store initialization, invokers |
datastore | DataStore, the query DSL, the Postgres adapter, command transactions |
logstore | Command, context, event, message, activity, query, response, and idempotency logs |
transport | NATS, Redis, Postgres LISTEN/NOTIFY, and in-process buses; RPC dispatch actors |
config, cfgfmt, cfgfetch | Runtime configuration, HCL/YAML/TOML parsing, remote config |
applog, otel, pool, util | Logging, telemetry hooks, shared runtime utilities |
macro | domain!, command_engine!, query_resource!, report_resource!, entity macros |
Authentication, Casbin, OpenAPI, and realtime adapters live in riverbase_http,
so a worker or CLI can use the kernel without pulling in axum.
Layers inside an application
- Domain models own state, invariants, and domain events.
- Application handlers, meaning command handlers and process managers, own orchestration and the local unit of work.
- Infrastructure implements stores, the outbox, transports, and provider gateways.
- Adapters (HTTP, workers, internal invokers) translate requests and establish identity. They hold no business rules.
- Composition (your service's
main) wires concrete implementations together.
Design goals
- Explicit consistency boundaries. One bounded context owns each aggregate root and each local command transaction.
- Separate write and read paths. Commands go through aggregates; queries and reports go through read models.
- Transport-independent domains. Domain code does not depend on axum or on cross-context dispatch.
- Explicit execution context. Namespace, actor, tenant, correlation, claims, and idempotency
travel in
EngineContext. - Policy on every entry path. HTTP, workers, tests, and internal calls all evaluate the target's policy, and fail closed when context is missing.
- Actors schedule; the database decides. Ractor pools give dispatch and backpressure; transactions, constraints, and optimistic versions give correctness.
Deployment modes
| Mode | What runs | How |
|---|---|---|
| Coupled | Command and query routes in one process | RiverbaseApp::mount_domain |
| Split | A command process and a query process for the same domain | mount_command in one binary, mount_query in another |
| Worker | Bus consumers and job runners, no HTTP | riverbase_task WorkerRuntime |
Every mode binds the same domain EngineContext. Moving a domain between processes does not change
ownership, transactions, or policy. See Deployment.