Skip to main content

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​

ModuleResponsibility
baseIDs, names, scopes, EngineContext, errors, aggregate and audit context, tenant policy
commandThe ractor command engine, command metadata, typed handlers, the aggregate session, the message-bus port, process managers
queryQuery and report resources, interface and binding, lowering to DataQuery, the query engine
domainThe Domain trait, engine traits and composites, DomainRuntime, store initialization, invokers
datastoreDataStore, the query DSL, the Postgres adapter, command transactions
logstoreCommand, context, event, message, activity, query, response, and idempotency logs
transportNATS, Redis, Postgres LISTEN/NOTIFY, and in-process buses; RPC dispatch actors
config, cfgfmt, cfgfetchRuntime configuration, HCL/YAML/TOML parsing, remote config
applog, otel, pool, utilLogging, telemetry hooks, shared runtime utilities
macrodomain!, 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​

  1. Explicit consistency boundaries. One bounded context owns each aggregate root and each local command transaction.
  2. Separate write and read paths. Commands go through aggregates; queries and reports go through read models.
  3. Transport-independent domains. Domain code does not depend on axum or on cross-context dispatch.
  4. Explicit execution context. Namespace, actor, tenant, correlation, claims, and idempotency travel in EngineContext.
  5. Policy on every entry path. HTTP, workers, tests, and internal calls all evaluate the target's policy, and fail closed when context is missing.
  6. Actors schedule; the database decides. Ractor pools give dispatch and backpressure; transactions, constraints, and optimistic versions give correctness.

Deployment modes​

ModeWhat runsHow
CoupledCommand and query routes in one processRiverbaseApp::mount_domain
SplitA command process and a query process for the same domainmount_command in one binary, mount_query in another
WorkerBus consumers and job runners, no HTTPriverbase_task WorkerRuntime

Every mode binds the same domain EngineContext. Moving a domain between processes does not change ownership, transactions, or policy. See Deployment.